Firestore:使用 SnapshotListener 调用异步函数并在 DispatchGroup 的循环中调用会导致崩溃
Firestore: Calls with of async function with SnapshotListener and in a cycle with DispatchGroup cause a crash
我在将 DispatchGroup(因为它是 recommended here)与 FireStore snapshotListener
一起使用时遇到问题
在我的示例中,我有两个函数。第一个被 ViewController 调用并且应该 return 对象数组显示在视图中。
第二个是从 FireStore 中为每个数组成员获取子对象的函数。它们都必须异步执行。第二个应该循环调用。
所以我使用 DispatchGroup 等到第二个函数的所有执行都完成后调用 UI 更新。这是我的代码(见评论部分):
/// 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() 都已在此步骤中完成。而这个闭包是listener单独调用的。
我试图找到一种方法来检查 DispatchGroup 是否为空而不调用 leave() 方法。但是没有找到任何本机解决方案。
我发现的唯一类似解决方案是 。但它看起来太老套了,不确定是否能正常工作。
有什么方法可以检查DispatchGroup是否为空?根据this的回答,没有办法。但可能在过去 2 年发生了一些变化。
是否有任何其他方法可以解决此问题并保留 snapshotListener?
现在我实施了某种变通解决方案 - 使用计数器。
我觉得这不是最好的解决方案,但至少现在可以用。
// 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)
}
})
}
我在将 DispatchGroup(因为它是 recommended here)与 FireStore snapshotListener
一起使用时遇到问题在我的示例中,我有两个函数。第一个被 ViewController 调用并且应该 return 对象数组显示在视图中。 第二个是从 FireStore 中为每个数组成员获取子对象的函数。它们都必须异步执行。第二个应该循环调用。
所以我使用 DispatchGroup 等到第二个函数的所有执行都完成后调用 UI 更新。这是我的代码(见评论部分):
/// 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() 都已在此步骤中完成。而这个闭包是listener单独调用的。
我试图找到一种方法来检查 DispatchGroup 是否为空而不调用 leave() 方法。但是没有找到任何本机解决方案。
我发现的唯一类似解决方案是
有什么方法可以检查DispatchGroup是否为空?根据this的回答,没有办法。但可能在过去 2 年发生了一些变化。
是否有任何其他方法可以解决此问题并保留 snapshotListener?
现在我实施了某种变通解决方案 - 使用计数器。 我觉得这不是最好的解决方案,但至少现在可以用。
// 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)
}
})
}