简体   繁体   English

Firestore:使用SnapshotListener调用异步函数,并在DispatchGroup循环中导致崩溃

[英]Firestore: Calls with of async function with SnapshotListener and in a cycle with DispatchGroup cause a crash

I have an issue with using DispatchGroup (as it was recommended here ) with FireStore snapshotListener 使用FireStore snapshotListener时使用DispatchGroup(如此处所推荐的 )有一个问题

In my example I have two functions. 在我的例子中,我有两个功能。 The first one is being called by the ViewController and should return array of objects to be displayed in the View. 第一个是由ViewController调用的,应该返回要在View中显示的对象数组。 The second one is a function to get child object from FireStore for each array member. 第二个是从每个数组成员的FireStore获取子对象的函数。 Both of them must be executed asynchronously. 它们都必须异步执行。 The second one should be called in cycle. 第二个应该在循环中调用。

So I used DispatchGroup to wait till all executions of the second function are completed to call the UI update. 所以我使用DispatchGroup等待第二个函数的所有执行完成以调用UI更新。 Here is my code (see commented section): 这是我的代码(请参阅注释部分):

/// 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)
    }
}

Everything works fine till the moment when the snapshot is changed because of using a snapshotListener in the second function. 一切正常,直到由于在第二个函数中使用snapshotListener而更改快照的时刻。 When data is changed, the following closure is getting called: 更改数据时,将调用以下闭包:

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

And it fails on the dispatchGroup.leave() step, because at the moment group is empty. 并且它在dispatchGroup.leave()步骤失败,因为此时组是空的。

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

All dispatchGroup.enter() and dispatchGroup.leave() are already done on this step. 所有dispatchGroup.enter()和dispatchGroup.leave()都已在此步骤中完成。 And this closure was called by listener separately. 这个闭包是由听众分别调用的。

I tried to find the way how to check if the DispatchGroup is empty to do not call leave() method. 我试图找到如何检查DispatchGroup是否为空以便不调用leave()方法的方法。 But did not find any native solution. 但没有找到任何原生解决方案。 The only similar solution I've found is in the following answer . 我发现的唯一类似解决方案是在以下答案中 But it looks too hacky and not sure if will work properly. 但它看起来太hacky并且不确定是否能正常工作。

Is there any way to check if DispatchGroup is empty? 有没有办法检查DispatchGroup是否为空? According to this answer, there is no way to do it. 根据这个答案,没有办法做到这一点。 But probably something changed during last 2 years. 但可能在过去的两年里发生了一些变化。

Is there any other way to fix this issue and keep snapshotListener in place? 有没有其他方法来解决此问题并保持snapshotListener到位?

For now I implemented some kind of workaround solution - to use a counter. 现在我实现了某种解决方案 - 使用计数器。 I do not feel it's the best solution, but at least work for now. 我觉得这不是最好的解决方案,但至少目前还在努力。

// 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)
        }
    })
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM