У меня проблема с использованием 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 на месте?