Firebase & Swift: асинхронные вызовы, обработчики завершения - PullRequest
0 голосов
/ 23 января 2019

Я много читал на эту тему, но все еще был озадачен этой конкретной проблемой. У меня есть много вызовов 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 RankingReceived == 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 и запустить метод расчета, прежде чем перейти к следующему мему. Мне нужно завершить of 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 голосов
/ 25 января 2019

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

0 голосов
/ 23 января 2019

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

Счастливого кодирования!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...