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

Arrays 等待循环完成,DispatchGroup使用Swift从firebase获取数据,arrays,swift,firebase,swiftui,Arrays,Swift,Firebase,Swiftui,我正试图招募一批员工,并等待它完成 尝试使用DispatchGroup,但似乎什么也没发生 有一个二维数组,第一个在员工上方,第二个在他成功收到的文件上。然后,它应该将ShiftConst对象附加到请求的数组中 我希望它在完成时使用前面的完成块进行通知 我得到的结果是0和“未完成” 这是我的密码: @Published var employeesConst = [ShiftConst]() func getAllShiftConsts(employees: [Employee

我正试图招募一批员工,并等待它完成

尝试使用
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)
            
        }

不要这样做,因为您阻塞了主线程,使其对所有内容都没有响应,而是在准备就绪时异步调度结果,如

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

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

一个更次要的观察是,您也在
for
循环中使用组,但这不是必需的,除非您在该循环中执行任何异步操作。所以,除非你的
for
循环触发了额外的异步任务,否则我不会把它和组纠缠在一起

因此,底线是,在异步调用之前调用
enter
,然后在调用完成时离开(在本例中,最好使用
defer
,IMHO)

例如

func getAllShiftConsts(员工:[员工],完成:((结果)->无效)?=nil){
let group=DispatchGroup()
var员工:[ShiftConst]=[]
变量错误:[错误]=[]
员工中的员工{
let ref=session.db.collection(CollectionRef.users.rawValue).document(employee.uid!).collection(CollectionRef.shiftConsts.rawValue)
group.enter()
ref.getDocuments{qSnapshot,错误在
延迟{group.leave()}
守卫让qSnapshot=qSnapshot,!qSnapshot.isEmpty else{return}
对于qSnapshot.documents{
做{
let data=try document.decode(as:ShiftConst.self)
employees.append(数据)
}捕捉错误{
错误。追加(错误)
}
}
}
}
通知组(队列:.main){
如果let error=errors.first{
完成?(.failure(error))//我不知道你想对所有错误做什么,所以我就抓住第一个
}否则{
完成?(.成功(员工))
}
}
}
上述内容中的一些其他小修改:

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

  • 如果闭包是可选的,而不是将其默认为
    {in}
    ,那么实际上可以将其设置为可选的,使用
    完成(…)
    语法调用它。就个人而言,我不会将其设置为可选的(以便调用方负责更新模型),但我将其设置为可选的,以匹配您的代码示例

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


  • 但是这个方法不允许我在抓取完成时使用完成块。为什么在这个用例中需要完成?使用上面的方法,用户会在一些数据出现时立即在UI中得到一些更新,而不是在完成时的最后一刻。我已经添加了我想应用于这个问题的工作。我觉得这是完成这项任务所需的唯一途径。当然可能有更好的方法,但现在我想这就是我能做到的。非常感谢你的解决方案和解释!现在可以了!快速提问,是否不建议使用此调度组方法?因为它阻塞了主线程。有没有更好的方法来实现这个目标?@ElaiZuberman因为你使用的是
    notify
    ,所以你没有阻止任何东西。当然,我们还可以提出其他改进建议,但我在上面的评论中谈到了我所认为的实质性问题。
    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))
            }
        }
    }