Firestore: вызовы с асинхронной функцией с SnapshotListener и в цикле с DispatchGroup вызывают сбой - PullRequest
0 голосов
/ 17 июня 2019

У меня проблема с использованием DispatchGroup (как это было рекомендуется здесь ) с FireStore snapshotListener

В моем примере у меня есть две функции.Первый вызывается ViewController и должен возвращать массив объектов для отображения в представлении.Вторая - это функция для получения дочернего объекта из FireStore для каждого члена массива.Оба они должны быть выполнены асинхронно.Второй должен вызываться в цикле.

Поэтому я использовал DispatchGroup, чтобы дождаться завершения всех выполнений второй функции, чтобы вызвать обновление пользовательского интерфейса.Вот мой код (см. Раздел с комментариями):

/// Async function returns all tables with active sessions (if any)
class func getTablesWithActiveSessionsAsync(completion: @escaping ([Table], Error?) -> Void) {

    let tablesCollection = userData
        .collection("Tables")
        .order(by: "name", descending: false)

    tablesCollection.addSnapshotListener { (snapshot, error) in
        var tables = [Table]()

        if let error = error {
            completion (tables, error)
        }

        if let snapshot = snapshot {
            for document in snapshot.documents {
                let data = document.data()
                let firebaseID = document.documentID
                let tableName = data["name"] as! String
                let tableCapacity = data["capacity"] as! Int16

                let table = Table(firebaseID: firebaseID, tableName: tableName, tableCapacity: tableCapacity)
                tables.append(table)
            }
        }

        // Get active sessions for each table.
        // Run completion only when the last one is processed.
        let dispatchGroup = DispatchGroup()

        for table in tables {
            dispatchGroup.enter()
            DBQuery.getActiveTableSessionAsync(forTable: table, completion: { (tableSession, error) in
                if let error = error {
                    completion([], error)
                    return
                }
                table.tableSession = tableSession
                dispatchGroup.leave()
            })
        }
        dispatchGroup.notify(queue: DispatchQueue.main) {
            completion(tables, nil)
        }
    }
}

/// Async function returns table session for table or nil if no active session is opened.
class func getActiveTableSessionAsync (forTable table: Table, completion: @escaping (TableSession?, Error?) -> Void) {

    let tableSessionCollection = userData
        .collection("Tables")
        .document(table.firebaseID!)
        .collection("ActiveSessions")

    tableSessionCollection.addSnapshotListener { (snapshot, error) in
        if let error = error {
            completion(nil, error)
            return
        }
        if let snapshot = snapshot {
            guard snapshot.documents.count != 0 else { completion(nil, error); return }

        // some other code

        }
        completion(nil,nil)
    }
}

Все работает нормально до того момента, когда снимок изменяется из-за использования snapshotListener во второй функции.Когда данные изменяются, вызывается следующее закрытие:

DBQuery.getActiveTableSessionAsync(forTable: table, completion: { (tableSession, error) in
    if let error = error {
        completion([], error)
        return
    }
    table.tableSession = tableSession
    dispatchGroup.leave()
})

И оно завершается ошибкой на шаге dispatchGroup.leave (), потому что в данный момент группа пуста.

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

Все dispatchGroup.enter () и dispatchGroup.leave () уже выполнены на этом шаге.И это закрытие вызывалось слушателем отдельно.

Я попытался найти способ проверить, является ли DispatchGroup пустым, чтобы не вызывать метод exit ().Но не нашел ни одного родного решения.Единственное подобное решение, которое я нашел, это следующий ответ .Но это выглядит слишком странно и не уверен, что будет работать правильно.

Есть ли способ проверить, пуста ли DispatchGroup?Согласно этому ответу, нет способа сделать это.Но, возможно, что-то изменилось за последние 2 года.

Есть ли другой способ исправить эту проблему и сохранить snapshotListener на месте?

1 Ответ

0 голосов
/ 17 июня 2019

Пока я реализовал какое-то обходное решение - использовать счетчик. Я не чувствую, что это лучшее решение, но, по крайней мере, пока работай.

// Get active sessions for each table.
// Run completion only when the last one is processed.
var counter = tables.count

for table in tables {
    DBQuery.getActiveTableSessionAsync(forTable: table, completion: { (tableSession, error) in
        if let error = error {
            completion([], error)
            return
        }
        table.tableSession = tableSession

        counter = counter - 1
        if (counter <= 0) {
            completion(tables, nil)
        }
    })
}
...