DispatchGroup结合forEach循环时Firestore函数未调用的问题
问题分析与修复方案
核心问题
- 死锁阻塞:你用
group.wait()同步阻塞了当前线程,如果这个函数在主线程调用,Firestore的回调任务会因主线程被卡死无法执行,导致group.leave()永远触发不了,wait就一直卡住。 - 错误分支遗漏leave:Firestore请求出错时,你直接调用
completion返回,但没执行group.leave(),DispatchGroup会一直等这个任务的leave信号,同样造成阻塞。 - 线程不安全:多个异步线程同时对
plays数组执行append,会引发数据竞争,导致数组数据损坏或崩溃。
修复后的代码
func getUserGlobalPlays(username: String, fixtureIDs: [Int], completion: @escaping (Result<[UserPlays]?, Error>) -> Void) { let chunkedArray = fixtureIDs.chunked(into: 10) var plays: [UserPlays] = [] let group = DispatchGroup() // 串行队列保证数组操作的线程安全 let playsQueue = DispatchQueue(label: "com.yourApp.playsQueue") chunkedArray.forEach { ids in group.enter() print("entered") DispatchQueue.global().async { [weak self] in guard let self = self else { group.leave() return } self.db.collection("Users").document("\(username)").collection("userPlays") .whereField("fixtureID", in: ids) .getDocuments { snapshot, error in // 不管成功失败都执行leave,避免遗漏 defer { group.leave() print("leaving") } guard let snapshot = snapshot, error == nil else { completion(.failure(error!)) return } // 用compactMap过滤无效数据,避免强制解包崩溃 let newPlays = snapshot.documents.compactMap { document -> UserPlays? in guard let fixtureIDx = document["fixtureID"] as? Int, let choice = document["userChoice"] as? Int else { return nil } return UserPlays(fixtureID: fixtureIDx, userChoice: choice) } // 串行队列同步写入数组,避免多线程竞争 playsQueue.sync { plays.append(contentsOf: newPlays) } } } } // 用notify替代wait,异步等待所有任务完成,避免阻塞线程 group.notify(queue: .main) { print(plays.count) completion(.success(plays)) } }
关键修改点
- 替换
group.wait()为group.notify(queue: .main):异步等待任务完成,不会阻塞当前线程,从根源避免死锁。 - 新增
defer { group.leave() }:不管请求成功还是失败,都会触发leave,保证DispatchGroup计数正确。 - 加入
playsQueue串行队列:所有对plays的修改都通过这个队列同步执行,解决多线程数据竞争问题。 - 用
compactMap替代强制解包:过滤掉格式错误的数据,避免因数据异常导致崩溃。 - 补充
self判空逻辑:防止self提前释放时遗漏leave信号。
内容的提问来源于stack exchange,提问作者Mbasa
相关产品推荐
相关产品推荐

