Я много читал на эту тему, но до сих пор не могу решить эту конкретную проблему. У меня есть много вызовов 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, что нормально для этой функции. Однако updateScores не может работать до тех пор, пока не завершится сбор RankingValues. Вот почему у меня есть обработчик завершения. Я думаю, что эта область в порядке, основываясь на моей отладочной печати.
// 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 никогда не запускаются. Оба этих метода должны выполняться перед методом расчета. Я пытаюсь включить цикл "в то время как ratingReceived == false", чтобы расчет не начался. Я использую обработчик завершения для уведомления в self.rankingAreceived и self.rankingBreceived, когда значения были получены из базы данных. Вместо этого вычисление никогда не происходит, и цикл становится бесконечным.
Если я уберу цикл while, ожидающий получения ранжирования, вычисления будут «выполнены», за исключением того, что конечный результат окажется нулевым, поскольку методы 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)
}
}
О, хорошо, стреляй, это имеет смысл. Я сделаю это в следующий раз. Спасибо!
Вы можете использовать промисы. Например, github.com/google/обещанияgithub.com/mxcl/PromiseKit
Чтобы дождаться завершения цикла или, что еще лучше, сделать что-то после выполнения какого-либо асинхронного вызова, вы можете использовать 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...")
}
}
ОБНОВИТЬ
В этом примере показано, как группа используется для управления выводом. Нет необходимости ждать () или что-то в этом роде. Вы просто вводите группу в каждой итерации цикла, оставляете ее в асинхронных обратных вызовах, и когда каждая задача покидает группу, вызывается 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() вместо цикла «в то время как self.rankingsReceived == false», чтобы основной поток не двигался дальше, я вижу тот же результат, что и при наличии цикла. Я добавил дополнительные операторы печати и обнаружил, что функции getRankingA и getRankingB вызываются, как и ожидалось, но код застревает во время выполнения в нихObservSingleEvents. Если я переместил завершение () перед наблюдениемSingleEvents в getRankingA и getRankingB, все будет работать в правильном порядке в любом случае, но вызов Firebase не будет выполнен.
Я не могу понять, почему вызовы Firebase внутри getRankingA и getRankingB заставляют мой код приостанавливаться. Это не ошибка, это похоже на бесконечный цикл. Когда я получаю другие значения, такие как идентификаторы пользователей и мемов, в предыдущих вызовах, значения принимаются, как и ожидалось. Любые идеи? Теперь я действительно в тупике.
Большое спасибо! Это решило одну из проблем и помогло мне разобраться с другой. Вы можете увидеть, что, наконец, сработало в ответе, который я только что опубликовал. Очень признателен!
Ответ Томте решил одну из моих проблем (спасибо!). Вычисления будут производиться после получения 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)
}
Мне также пришлось добавить обработчик завершения в метод расчета, чтобы гарантировать, что метод updateScores будет вызываться после того, как будут проведены вычисления userAranking и userBranking, а не только после того, как они будут получены из базы данных.
Yay для диспетчерских групп!
Рад, что это помогло вам. Вы должны обновить свой вопрос и принять данный ответ в следующий раз. Удачного кодирования!
Пожалуйста, не редактируйте свой пост как дополнение к ответам; вместо этого задайте новый вопрос.