Firebase и Swift: асинхронные вызовы, обработчики завершения

Я много читал на эту тему, но до сих пор не могу решить эту конкретную проблему. У меня есть много вызовов 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)
}

}

Пожалуйста, не редактируйте свой пост как дополнение к ответам; вместо этого задайте новый вопрос.

jscs 23.01.2019 22:12

О, хорошо, стреляй, это имеет смысл. Я сделаю это в следующий раз. Спасибо!

jordan_amanda 23.01.2019 22:13

Вы можете использовать промисы. Например, github.com/google/обещанияgithub.com/mxcl/PromiseKit

Alexander Khitev 23.01.2019 22:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
425
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Чтобы дождаться завершения цикла или, что еще лучше, сделать что-то после выполнения какого-либо асинхронного вызова, вы можете использовать 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

jordan_amanda 23.01.2019 18:02

Хм, я понимаю, что у меня сейчас другая проблема. Когда я использую группу диспетчеризации wait() вместо цикла «в то время как self.rankingsReceived == false», чтобы основной поток не двигался дальше, я вижу тот же результат, что и при наличии цикла. Я добавил дополнительные операторы печати и обнаружил, что функции getRankingA и getRankingB вызываются, как и ожидалось, но код застревает во время выполнения в нихObservSingleEvents. Если я переместил завершение () перед наблюдениемSingleEvents в getRankingA и getRankingB, все будет работать в правильном порядке в любом случае, но вызов Firebase не будет выполнен.

jordan_amanda 23.01.2019 21:18

Я не могу понять, почему вызовы Firebase внутри getRankingA и getRankingB заставляют мой код приостанавливаться. Это не ошибка, это похоже на бесконечный цикл. Когда я получаю другие значения, такие как идентификаторы пользователей и мемов, в предыдущих вызовах, значения принимаются, как и ожидалось. Любые идеи? Теперь я действительно в тупике.

jordan_amanda 23.01.2019 21:20

Большое спасибо! Это решило одну из проблем и помогло мне разобраться с другой. Вы можете увидеть, что, наконец, сработало в ответе, который я только что опубликовал. Очень признателен!

jordan_amanda 25.01.2019 19:56
Ответ принят как подходящий

Ответ Томте решил одну из моих проблем (спасибо!). Вычисления будут производиться после получения 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 для диспетчерских групп!

raywenderlich.com/… Эта ссылка тоже очень помогла!
jordan_amanda 25.01.2019 19:57

Рад, что это помогло вам. Вы должны обновить свой вопрос и принять данный ответ в следующий раз. Удачного кодирования!

Tomte 26.01.2019 20:23

Другие вопросы по теме