等待循环完成,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 异步调用
如何等待 requestLocationUpdates() 回调意图完成?
- swift:async + JSON + completion + DispatchGroup