使用DispatchGroup填充对象数组失效,求技术排查建议
我来帮你分析下这个问题,你遇到的DispatchGroup失效、返回空数组的情况,主要是嵌套异步操作的计数错误和错误处理逻辑不当导致的,咱们一步步拆解修复:
核心问题定位
- DispatchGroup计数混乱:处理Item加载时,一旦加载失败调用
completion(nil),却没有对应调用dispatchGroup.leave(),导致DispatchGroup的计数器永远无法归零,notify代码块永远不会执行,最终要么一直等待,要么提前返回空数组。 - 错误处理重复调用:当前代码中任何一个Customer或Item加载失败都会立即调用
completion(nil),可能出现多次调用completion的情况,不符合回调的原子性要求。 - 潜在的引用/副本问题:虽然
Ticket1如果是类(引用类型),循环里的var ticket是引用副本,但嵌套异步操作中,可能出现ticket还没完成Item加载就被加入result的情况(不过这一点被DispatchGroup的问题掩盖了)。
修复后的代码示例
static func loadAll(_ completion: @escaping ([Ticket1]?) -> Void) -> UInt { return FirebaseService.instance.readAll(from: .tickets, returning: Ticket1.self, { (tickets) in guard let tickets = tickets else { completion(nil) return } var result = [Ticket1]() let mainDispatchGroup = DispatchGroup() var hasLoadError = false // 标记是否出现加载错误,确保completion只调用一次 for var ticket in tickets { mainDispatchGroup.enter() Customer.loadSingleObject(withID: ticket.customerID, { (customer) in // 已经出错的话直接终止当前ticket的处理 guard !hasLoadError, let customer = customer else { hasLoadError = true mainDispatchGroup.leave() return } ticket.customer = customer // 为当前ticket的所有Item创建独立的子DispatchGroup,避免嵌套计数混乱 let itemLoadGroup = DispatchGroup() for itemID in ticket.itemsID { itemLoadGroup.enter() Item.loadSingleObject(withID: itemID, { (item) in // 无论成功失败都确保leave执行,避免计数器卡死 defer { itemLoadGroup.leave() } guard !hasLoadError, let item = item else { hasLoadError = true return } // 提前初始化items数组,避免nil导致append失效 if ticket.items == nil { ticket.items = [] } ticket.items?.append(item) }) } // 等当前ticket的所有Item加载完成后,再加入结果集并标记主Group的leave itemLoadGroup.notify(queue: .global()) { guard !hasLoadError else { mainDispatchGroup.leave() return } result.append(ticket) mainDispatchGroup.leave() } }) } mainDispatchGroup.notify(queue: .main) { if hasLoadError { completion(nil) } else { let sortedTickets = result.sorted(by: { $0.createdAt > $1.createdAt }) completion(sortedTickets) } } }) }
关键修复点说明
- 子DispatchGroup隔离Item加载:给每个ticket的Item加载单独创建子Group,确保所有Item加载完成后,再将ticket加入结果集,彻底避免嵌套计数混乱。
- defer强制执行leave:在Item的回调中用
defer确保无论加载成功还是失败,都能执行leave,保证计数器正常归零。 - 错误原子性控制:用
hasLoadError标记错误状态,一旦出错就终止后续所有异步任务的处理,且确保completion只被调用一次。 - 提前初始化items数组:如果
Ticket1的items属性默认是nil,提前赋值为空数组,避免append操作被跳过。
额外排查建议
- 检查
Ticket1的items属性初始化逻辑,确保它不是默认nil,否则append操作会直接失效。 - 在每个
enter和leave处添加打印日志(比如print("Main group enter")),查看计数器是否能正确归零,定位是否还有遗漏的leave操作。 - 确认
Item.loadSingleObject的实现是否可靠,是否在所有场景下(包括网络失败、数据不存在)都会调用回调,避免出现回调不执行导致leave永远不被调用的情况。
内容的提问来源于stack exchange,提问作者Daniel Carriola
相关产品推荐
相关产品推荐

