等待循环完成,DispatchGroup 使用 Swift 从 firebase 获取数据

Posted

技术标签:

【中文标题】等待循环完成,DispatchGroup 使用 Swift 从 firebase 获取数据【英文标题】:Waiting for loop to finish with DispatchGroup getting data from firebase using Swift 【发布时间】:2020-09-21 16:14:11 【问题描述】:

我正在尝试获取一组员工并等待它完成。

尝试使用DispatchGroup,但似乎什么也没发生。

有一个二维数组,第一个是关于员工的,第二个是关于他成功收到的文件的。然后,它应该将ShiftConst 对象附加到请求的数组中。

我希望它通过使用前面的完成块来通知它何时完成。

我得到的结果是 0 并且“没有完成”。

这是我的代码:


    @Published var employeesConst = [ShiftConst]()

    func getAllShiftConsts(employees: [Employee], completion: @escaping (Bool) -> () = _ in)
        
        let group = DispatchGroup()
        var finish = false
        
        for employee in employees
            let ref = self.session.db.collection(CollectionRef.users.rawValue).document(employee.uid!).collection(CollectionRef.shiftConsts.rawValue)
            
            ref.getDocuments  (qSnapshot, error) in
                
                guard let qSnapshot = qSnapshot, !qSnapshot.isEmpty else return
                
                for document in qSnapshot.documents
                    group.enter()
                    do
                        let data = try document.decode(as: ShiftConst.self)
                        self.employeesConst.append(data)
                        finish = true
                        group.leave()
                    catch let error
                        finish = false
                        group.leave()
                        print(error)
                    
                    
                
            
        
        
        group.notify(queue: .main) 
            completion(finish)
        
    

用法:

getAllShiftConsts(employees: session.employeesList)  finish in
            if finish 
                print("finish")
                print("Count: ", self.employeesConst.count)
            else
                print("didn't finish")
                print("Count: ", self.employeesConst.count)
            
        

完成后我想用它来做这个:

for (index, symbol) in self.date.weekDaySymbols.enumerated()
            
            let day = Day(date: self.date.nextWeek[index], isSelected: false, hasShifts: false, daySymbol: symbol, dayNumber: self.date.nextWeekNumbers[index])
            
            day.setShifts(morning: self.session.employeesList, middle: self.session.employeesList, evening: self.session.employeesList)
            
            for const in self.employeesConst
                if const.dateToString() == day.date
                    for shift in day.shifts
                        if shift.type == const.shiftType
                            shift.filterEmployees(shiftConsts: const)
                        
                    
                
            
            
            if index == 0 day.isSelected = true
            
            self.week.append(day)
            
        

【问题讨论】:

【参考方案1】:

问题在于notify 确定它迄今为止遇到的所有enter 调用是否都被等量的leave 调用所抵消。但是您的enter 调用异步ref.getDocuments 调用中,因此无疑在到达第一个enter 之前到达notify。因此,它会立即触发notify 块,根本不需要等待。

一个更小的观察结果是,您也在 for 循环中使用了组,但除非您在该循环中执行任何异步操作,否则这不是必需的。因此,除非您的 for 循环触发了额外的异步任务,否则我不会将它与组纠缠在一起。

因此,最重要的是,在异步调用之前调用enter,并在调用完成后调用leave(在这种情况下,最好使用defer,恕我直言)。

例如

func getAllShiftConsts(employees: [Employee], completion: ((Result<[ShiftConst], Error>) -> Void)? = nil) 
    let group = DispatchGroup()
    var employees: [ShiftConst] = []
    var errors: [Error] = []
    
    for employee in employees 
        let ref = session.db.collection(CollectionRef.users.rawValue).document(employee.uid!).collection(CollectionRef.shiftConsts.rawValue)

        group.enter()
        
        ref.getDocuments  qSnapshot, error in
            defer  group.leave() 
            
            guard let qSnapshot = qSnapshot, !qSnapshot.isEmpty else  return 
            
            for document in qSnapshot.documents      
                do 
                    let data = try document.decode(as: ShiftConst.self)
                    employees.append(data)
                 catch let error 
                    errors.append(error)
                
            
        
    
    
    group.notify(queue: .main) 
        if let error = errors.first 
            completion?(.failure(error))       // I don't know what you want to do with all the errors, so I'll just grab the first one
         else 
            completion?(.success(employees))
        
    

上面还有一些其他的小修改:

    我建议不要在 for 循环内更新 self.employeesConst。您通常不希望有一个异步过程来更新模型对象。您通常需要明确的职责分离,该函数负责检索结果数组,但它不应该更新模型对象。我会将数组构建为局部变量并将结果传递回闭包中。

    如果闭包是可选的,而不是将其默认为 _ in ,您实际上可以将其设为可选,使用completion?(...) 语法调用它。就个人而言,我不会将其设为可选(以便调用者负责更新模型),但我将其设为可选以匹配您的代码示例。

    我不知道你想在出现错误的情况下做什么,所以我只是构建了一个错误数组并在.failure 条件中提供了第一个错误。再说一遍,随心所欲。

【讨论】:

非常感谢您的解决方案和解释!现在可以了!快速提问,不建议使用这种调度组方法吗?因为它阻塞了主线程。有没有更好的方法来实现这个目标? @ElaiZuberman 因为您使用的是notify,所以您没有阻止任何内容。当然,我们可以提出其他改进建议,但我在上面的 cmets 中解决了我认为的实质性问题。【参考方案2】:

不要这样做,因为你阻塞了主线程,使它对所有东西都没有响应,而是在准备好时异步调度结果,比如

func getAllShiftConsts(employees: [Employee], completion: @escaping (Bool) -> () = _ in)
    
    for employee in employees
        let ref = self.session.db.collection(CollectionRef.users.rawValue).document(employee.uid!).collection(CollectionRef.shiftConsts.rawValue)
        
        ref.getDocuments  (qSnapshot, error) in
            guard let qSnapshot = qSnapshot, !qSnapshot.isEmpty else return
            for document in qSnapshot.documents
                do
                    let data = try document.decode(as: ShiftConst.self)
                   DispatchQueue.main.async 
                     self.employeesConst.append(data)
                   
                catch let error
                    print(error)
                
            
        
    

【讨论】:

但是这个方法不会让我在获取完成时使用完成块。 为什么在这个用例中需要补全?使用上述方法,您的用户将在一些数据出现后立即在 UI 中获得一些更新,而不是在完成时的最后。 因为我想在它完成后再次循环使用这个数组。我已经添加了我想应用于这个问题的工作。我觉得这是完成这项任务所需的唯一方法。当然可能有更好的方法,但现在我认为会这样做。

以上是关于等待循环完成,DispatchGroup 使用 Swift 从 firebase 获取数据的主要内容,如果未能解决你的问题,请参考以下文章

使用 DispatchGroup、DispatchQueue 和 DispatchSemaphore 按顺序执行带有 for 循环的 Swift 4 异步调用

如何在执行下一个操作之前等待请求完成(Xcode 11)

如何等待 requestLocationUpdates() 回调意图完成?

- swift:async + JSON + completion + DispatchGroup

Grand Central Dispatch 用于复杂流程?

等待循环中调用的所有承诺完成