Firebase 和 Swift:异步调用、完成处理程序

Posted

技术标签:

【中文标题】Firebase 和 Swift:异步调用、完成处理程序【英文标题】:Firebase & Swift: Asynchronous calls, Completion Handlers 【发布时间】:2019-01-23 08:27:56 【问题描述】:

我已经阅读了很多关于这个主题的内容,但仍然对这个具体问题感到困惑。我有许多相互依赖的 Firebase 调用。这是我的代码的一种简化示例。我很难把它写得更短,但仍然能理解重点:

class ScoreUpdater 

static let ref = Database.database().reference()

var userAranking = Int?
var userBranking = Int?
var rankingAreceived = false
var rankingBreceived = false
var sum = 0

// Pass in the current user and the current meme 
static func beginUpdate(memeID: String, userID: String) 

    // Iterate through each user who has ranked the meme before
    ScoreUpdater.ref.child("memes/\(memeID)/rankings")observeSingleEvent(of: .value) 

        let enumerator = snapshot.children
        while let nextUser = enumerator.nextObject() as? DataSnapshot 

            // Create a currentUpdater instance for the current user paired with each other user
            let currentUpdater = ScoreUpdater()

这是异步调用开始的地方。多个gatherRankingValues 函数可以同时运行。这个函数包含一个异步的 Firebase 调用,这对于这个函数是可以的。然而,在 collectRankingValues 完成之前,updateScores 无法运行。这就是为什么我有完成处理程序。根据我的调试打印,我认为这个区域还可以。

            // After gatherRankingValues is finished running,
            // then updateScores can run
            currentUpdater.gatherRankingValues(userA: userID, userB: nextUser.key as! String) 
                currentUpdater, userA, userB in
                currentUpdater.updateScores(userA: userA, userB:userB)
            

        

    



func gatherRankingValues(userA: String, userB: String, completion: @escaping (_ currentUpdater: SimilarityScoreUpdater, _ userA: String, _ userB: String) -> Void) 

    // Iterate through every meme in the database
    ScoreUpdater.ref.child("memes").observeSingleEvent(of: .value) 

        snapshot in
        let enumerator = snapshot.children

        while let nextMeme = enumerator.nextObject() as? DataSnapshot 

这是主要问题所在。self.getRankingA 和 self.getRankingB 永远不会运行。这两种方法都需要在计算方法之前运行。我尝试放入“whilerankingReceived == false”循环以防止计算开始。当从数据库接收到值时,我使用完成处理程序在 self.rankingAreceived 和 self.rankingBreceived 中进行通知。相反,计算永远不会发生,循环变得无限。

如果我删除等待接收排名的 while 循环,则计算将“执行”,但最终结果为 nil,因为 getRankingA 和 getRankingB 方法仍然没有被调用。


            self.getRankingA(userA: userA, memeID: nextMeme.key) 
                self.rankingAreceived = true
            

            self.getRankingB(userB: userB, memeID: nextMeme.key) 
                self.rankingBreceived = true
            

            while self.rankingAreceived == false || self.rankingBreceived == false 
                continue
            

            self.calculation()

        

所以是的,每个 meme 在调用完成之前都会循环播放,但不会调用排名。 我不知道如何让循环等待 getRankingA 和 getRankingB 的排名以及计算方法在继续下一个 meme 之前运行。 我需要在循环通过所有模因后调用gatherRankingValues(见下文),但每次排名和计算也要在再次调用循环之前完成。 .. 如何在 getRankingA 和 getRankingB 完成处理程序中告诉 meme 迭代循环等待?

        // After every meme has been looped through for this pair of users, call completion
        completion(self, userA, userB)

    



function getRankingA(userA: String, memeID: String, completion: @escaping () -> Void) 

    ScoreUpdater.ref.child("memes/\(memeID)\rankings\(userA)").observeSingleEvent(of: .value) 
        snapshot in
        self.userAranking = snapshot.value
        completion()

    


function getRankingB(userB: String, memeID: String, completion: @escaping () -> Void) 

    ScoreUpdater.ref.child("memes/\(memeID)\rankings\(userB)").observeSingleEvent(of: .value) 
        snapshot in
        self.userBranking = snapshot.value
        completion()

    


func calculation() 
    self.sum = self.userAranking + self.userBranking
    self.userAranking = nil
    self.userBranking = nil


func updateScores() 
    ScoreUpdater.ref.child(...)...setValue(self.sum)



【问题讨论】:

请不要编辑您的帖子作为对答案的跟进;而是提出一个新问题。 哦,好吧,拍摄是有道理的。下次我会这样做。谢谢! 你可以使用承诺。例如github.com/google/promisesgithub.com/mxcl/PromiseKit 【参考方案1】:

要等待循环完成或更好,在执行一些异步调用后做一些事情,你可以使用 DispatchGroups。这个例子展示了它们是如何工作的:

let group = DispatchGroup()

var isExecutedOne = false
var isExecutedTwo = false

group.enter()
myAsyncCallOne() 
    isExecutedOne = true
    group.leave()


group.enter()
myAsyncCallTwo() 
    isExecutedOTwo = true
    group.leave()


group.notify(queue: .main) 
    if isExecutedOne && isExecutedTwo 
        print("hooray!")
     else 
        print("nope...")
    

更新

此示例向您展示了如何使用组来控制输出。没有必要 wait() 什么的。您只需在循环的每次迭代中输入组,将其留在异步回调中,当每个任务离开组时,group.notify()被调用,您可以进行计算:

while let nextMeme = enumerator.nextObject() as? DataSnapshot 
    let group = DispatchGroup()

    group.enter()
    self.getRankingA(userA: userA, memeID: nextMeme.key) 
        self.rankingAreceived = true
        group.leave()
    

    group.enter()
    self.getRankingB(userB: userB, memeID: nextMeme.key) 
        self.rankingBreceived = true
        group.leave()
    

    // is called when the last task left the group
    group.notify(queue: .main) 
        self.calculation()
    


group.notify()在所有呼叫离开组时被调用。您也可以拥有嵌套组。

编码愉快!

【讨论】:

谢谢!!调度组是我研究过的一种解决方案,但在实施时遇到了麻烦。我今天会玩这个,如果我解决了这个问题,我会更新:D 嗯,我意识到我现在有一个不同的问题。当我使用调度组 wait() 代替“while self.rankingsReceived == false”循环来阻止主线程继续运行时,我看到的结果与我有循环时相同。我添加了更多打印语句,发现函数 getRankingA 和 getRankingB 按预期调用,但代码在其中的 observeSingleEvents 期间卡住了。如果我将completion() 移到getRankingA 和getRankingB 中的observeSingleEvents 之前,则无论哪种方式,一切都以正确的顺序运行,但不会进行Firebase 调用。 我不明白为什么 getRankingA 和 getRankingB 中的 Firebase 调用会导致我的代码暂停。没有错误,就像是一个无限循环。当我在以前的调用中收到其他值(例如 userID 和 memeID)时,会按预期收到这些值。有任何想法吗?现在我真的很难过。 非常感谢!这解决了其中一个问题并帮助我解决了另一个问题。您可以在我刚刚发布的答案中看到最终有效的方法。非常感谢!【参考方案2】:

Tomte 的回答解决了我的一个问题(谢谢!)。使用此代码收到 userAranking 和 userBranking 后将执行计算:

while let nextMeme = enumerator.nextObject() as? DataSnapshot 
    let group = DispatchGroup()

    group.enter()
    self.getRankingA(userA: userA, memeID: nextMeme.key) 
        self.rankingAreceived = true
        group.leave()
    

    group.enter()
    self.getRankingB(userB: userB, memeID: nextMeme.key) 
        self.rankingBreceived = true
        group.leave()
    

    // is called when the last task left the group
    group.notify(queue: .main) 
        self.calculation()
    


不过,对 updateScores 的完成调用将在循环结束时发生,但在收到所有 userArankings 和 userBrankings 之前以及在排名进行计算之前。我通过添加另一个调度组解决了这个问题:

let downloadGroup = DispatchGroup()

while let nextMeme = enumerator.nextObject() as? DataSnapshot 
    let calculationGroup = DispatchGroup()

    downloadGroup.enter()    
    calculationGroup.enter()
    self.getRankingA(userA: userA, memeID: nextMeme.key) 
        downloadGroup.leave()
        calculationGroup.leave()
    

    downloadGroup.enter()
    calculationGroup.enter()
    self.getRankingB(userB: userB, memeID: nextMeme.key) 
        downloadGroup.leave()
        calculationGroup.leave()
    

    // is called when the last task left the group
    downloadGroup.enter()
    calculationGroup.notify(queue: .main) 
        self.calculation() 
            downloadGroup.leave()
        
    



downloadGroup.notify(queue: .main) 
    completion(self, userA, userB)

我还必须在计算方法中添加一个完成处理程序,以确保在 userAranking 和 userBranking 进行计算时调用 updateScores 方法,而不仅仅是从数据库接收到它们。

为派遣组欢呼!

【讨论】:

raywenderlich.com/… 这个链接也很有帮助! 很高兴它对您有所帮助。您应该更新您的问题并下次接受给定的答案。编码愉快!

以上是关于Firebase 和 Swift:异步调用、完成处理程序的主要内容,如果未能解决你的问题,请参考以下文章

只有在 SwiftUI 和 Firebase 中完成循环中的异步调用时,如何才能返回函数?

如何使用 swift 和 firebase 正确使用完成处理程序?

Swift 异步方法和返回/完成块

等到带有异步网络请求的 swift for 循环完成执行

使用 Firebase 的 Swift 完成处理程序

Swift 3,Firebase 完成 Handler 执行两次