Swift-在调用完成处理程序之前等待异步for-in循环完成

3

我对如何实现这一点感到困惑。我正在尝试通过网络调用循环遍历并相加Double值的运行总和。我读过的所有内容都建议使用DispatchGroup。我的完成要么过早地调用,要么根本不被调用,我已经尝试了我能想到的所有.enter、.leave和.wait的配置。

    let group = DispatchGroup()
    var runningTotal: Double = 0.00

    ref.observeSingleEvent(of: .value) { (snapshot) in
        guard let bills = snapshot.value as? [String: AnyObject] else {
            //error
            return
        }

        for billId in bills.keys {
            group.enter()
            print("Entering")
            Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value, with: { (snapshot) in
                guard let bill = snapshot.value as? [String: AnyObject] else {
                    return
                }
                if let amount = bill["amount"] as? Double {
                    runningTotal += amount
                }
                group.leave()
                print("Leaving")
            })
        }
        completion(runningTotal)
    }
    group.wait()
}
2个回答

3

几个想法:

  1. 避免从主线程调用wait。这种情况的使用场景非常有限。 notify是实现同样功能的更安全的方法。

  2. 确保在循环内的每个路径中都调用leave。可以使用defer块来实现这一点。

因此:

func foo(completion: @escaping (Double?) -> Void) {
    ref.observeSingleEvent(of: .value) { snapshot in
        guard let bills = snapshot.value as? [String: AnyObject] else {
            //error
            completion(nil)
            return
        }

        let group = DispatchGroup()
        var runningTotal = 0.0

        for billId in bills.keys {
            group.enter()
            print("Entering")
            Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value) { snapshot in
                defer { group.leave() }
                guard let bill = snapshot.value as? [String: AnyObject] else {
                    return
                }
                if let amount = bill["amount"] as? Double {
                    runningTotal += amount
                }
                print("Leaving")
            }
        }
        group.notify(queue: .main) {
            completion(runningTotal)
        }
    }
}

但是你绝对不想阻塞主线程。这样会导致看门狗进程杀死你的应用程序,最好的情况下只能提供次标准的用户体验。只需在此调用的完成处理程序(或类似模式)中调用下一个网络调用即可。使用异步模式来实现所需的依赖关系,而不是阻塞主线程。 - Rob
似乎您的notify调用不在observeSingleEvent闭包内。 - Rob
Rob,如果你不介意为我澄清一件事的话;如果我不想返回到主线程,那我是否应该根本不调用notify呢? - chenders
@rmaddy - 是的,这样更好(虽然不会改变行为)。完成。 - Rob
1
好的,我相信我们现在已经达成了共识。wait()是一个阻塞调用,我现在明白了。我想我需要学习的是“避免这种情况的模式”部分。我只开发了一年半,所以仍然在不断地学习中... - chenders
显示剩余7条评论

2
你应该等待所有组任务完成后,再调用完成块。
像下面这样。
    var runningTotal: Double = 0.00

    ref.observeSingleEvent(of: .value) { (snapshot) in
        guard let bills = snapshot.value as? [String: AnyObject] else {
            //error
            return
        }

        let group = DispatchGroup()
        for billId in bills.keys {
            group.enter()
            print("Entering")
            Database.database().reference().child("bills").child(billId).observeSingleEvent(of: .value, with: { (snapshot) in
                guard let bill = snapshot.value as? [String: AnyObject] else {
                    group.leave()
                    return
                }
                if let amount = bill["amount"] as? Double {
                    runningTotal += amount
                }
                group.leave()
                print("Leaving")
            })
        }
        group.wait()
        completion(runningTotal)
    }

尝试这样做时,完成(runningTotal)在循环一次之前立即返回。 - chenders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接