Firebase & Swift:异步调用、完成处理程序
Firebase & Swift: Asynchronous calls, Completion Handlers
我已经阅读了很多关于这个主题的文章,但仍然对这个特定问题感到困惑。我有许多相互依赖的 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 调用,这对这个函数来说是可以的。然而,在 gatherRankingValues 完成之前,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 从来没有 运行。这两种方法都需要在计算方法前加上运行。我尝试放入 "while rankingReceived == false" 循环以防止计算开始。当从数据库接收到值时,我使用完成处理程序在 self.rankingAreceived 和 self.rankingBreceived 中发出通知。相反,计算永远不会发生,循环变得无限。
如果我删除等待接收排名的 while 循环,计算将是 "carried out" 除了最终结果为零,因为 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 的排名以及 运行 的计算方法,然后再继续下一个模因。 我需要完成 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)
}
}
要等待循环完成或更好,在执行一些异步调用后做一些事情,您可以使用 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()
当所有呼叫都离开群组时被调用。您也可以有嵌套组。
编码愉快!
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 方法,而不仅仅是在从数据库接收到它们时调用。
派遣组赞!
我已经阅读了很多关于这个主题的文章,但仍然对这个特定问题感到困惑。我有许多相互依赖的 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 调用,这对这个函数来说是可以的。然而,在 gatherRankingValues 完成之前,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 从来没有 运行。这两种方法都需要在计算方法前加上运行。我尝试放入 "while rankingReceived == false" 循环以防止计算开始。当从数据库接收到值时,我使用完成处理程序在 self.rankingAreceived 和 self.rankingBreceived 中发出通知。相反,计算永远不会发生,循环变得无限。
如果我删除等待接收排名的 while 循环,计算将是 "carried out" 除了最终结果为零,因为 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 的排名以及 运行 的计算方法,然后再继续下一个模因。 我需要完成 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)
}
}
要等待循环完成或更好,在执行一些异步调用后做一些事情,您可以使用 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()
当所有呼叫都离开群组时被调用。您也可以有嵌套组。
编码愉快!
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 方法,而不仅仅是在从数据库接收到它们时调用。
派遣组赞!