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

时间:2019-01-23 08:27:56

标签: ios swift firebase asynchronous completionhandler

我已经阅读了很多有关此主题的内容,但仍然对这个特定问题感到困惑。我有许多彼此依赖的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()

这是异步调用开始的地方。可以同时运行多个collectRankingValues函数。此函数包含一个Firebase调用,该调用是异步的,对于此函数来说可以。但是updateScores直到collectRankingValues完成后才能运行。这就是为什么我有完成处理程序。根据我的调试打印,我认为这个区域还可以。

            // 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永远不会运行。这两种方法都需要在计算方法之前运行。我尝试放入“ while rankReceived == 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()

        }

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

        // 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)
}

}

2 个答案:

答案 0 :(得分:0)

要等待循环完成或更好,请在执行一些异步调用后执行一些操作,可以使用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()。您也可以有嵌套的组。

快乐编码!

答案 1 :(得分:0)

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方法。

同意派遣小组!