如何在执行序列中的下一项前等待Firebase Storage中的图片完成下载?
解决Firestore文档处理时图片异步下载顺序混乱的问题
看起来你遇到的核心问题是异步任务的并发执行导致完成顺序不可控:你的getImageFromFirebase函数里,多个图片下载任务是并行发起的,而当前的计数逻辑(currentPhotoNumber)没有正确追踪所有任务的完成状态,甚至可能在图片还没写完磁盘就提前触发了completion,导致downloadItems里的后续处理(比如Realm写入)跟着乱序了。
问题代码的核心缺陷
- 计数逻辑错误:你在
downloadImageTask的.success回调里递增currentPhotoNumber,但这个回调仅表示下载完成,不代表图片已经成功保存到磁盘(如果saveImage()是异步操作,问题会更严重)。 - 并行任务的完成判断不可靠:
if currentPhotoNumber == totalPhotosInList的判断放在了图片保存的代码块里,而并行任务的完成顺序是随机的,这个条件可能永远不会触发,或者在部分图片还没处理完就提前触发。 - 错误处理不完整:如果某个图片下载失败,当前逻辑会直接
return,导致completion永远不会被调用,后续流程卡住。
修复方案:使用DispatchGroup追踪所有异步任务
我们可以用DispatchGroup来管理所有的图片下载和保存任务,确保所有任务都完成(不管成功还是失败)后,再调用completion。同时调整错误处理逻辑,保证completion一定会被触发。
修改后的getImageFromFirebase函数
func getImageFromFirebase(id: Int, imageFirebaseStorageRef: String, folderPath: String, completion: @escaping(Bool) -> Void) { let storageRef = Storage.storage().reference(withPath: imageFirebaseStorageRef) let dispatchGroup = DispatchGroup() var hasError = false storageRef.listAll { (result, error) in guard let result = result, error == nil else { print("错误信息:\(error?.localizedDescription ?? "未知错误")") completion(false) return } let totalPhotosInList = result.items.count guard totalPhotosInList > 0 else { // 没有图片,直接返回成功 completion(true) return } for item in result.items { dispatchGroup.enter() // 进入分组,标记任务开始 let finalPhotoStorageRef = Storage.storage().reference(withPath: item.fullPath) let downloadImageTask = finalPhotoStorageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in defer { dispatchGroup.leave() // 不管成功失败,最终都离开分组 } if let error = error { print("下载图片失败:\(error.localizedDescription)") hasError = true return } guard let data = data, let downloadedImage = UIImage(data: data) else { print("图片数据无效") hasError = true return } // 保存图片到磁盘 self.homeFileManagerModel.imageName = item.name self.homeFileManagerModel.imageFolderPath = "\(folderPath)/\(id)" self.homeFileManagerModel.image = downloadedImage self.homeFileManagerModel.saveImage() print("已保存图片\(item.name)至本地磁盘") } // 进度监听可以保留 downloadImageTask.observe(.progress) { snapshot in let percentComplete = 100.0 * Double(snapshot.progress!.completedUnitCount) / Double(snapshot.progress!.totalUnitCount) print("图片\(item.name)下载进度:\(percentComplete)%") } } // 所有任务完成后触发completion dispatchGroup.notify(queue: .main) { completion(!hasError) // 如果有任何错误,返回false,否则返回true } } }
关键改动说明
- DispatchGroup管理并发任务:
- 每个图片下载任务开始前调用
dispatchGroup.enter(),任务结束(无论成功失败)调用dispatchGroup.leave()。 - 用
dispatchGroup.notify监听所有任务完成,确保所有图片都处理完后才调用completion。
- 每个图片下载任务开始前调用
- 完善错误处理:
- 新增
hasError变量追踪是否有任务失败,最终根据这个变量返回结果。 - 所有可能的错误分支都保证会触发
leave(),避免分组永远等待。
- 新增
- 适配异步保存场景:
如果saveImage()是异步方法,需要在它的completion回调里再调用dispatchGroup.leave(),示例如下:// 假设saveImage的异步实现 self.homeFileManagerModel.saveImage { [weak self] success in if !success { self?.hasError = true } dispatchGroup.leave() } - 空列表处理:当文件夹里没有图片时,直接返回成功,避免不必要的等待。
额外建议
- 如果你需要保证Firestore文档的处理顺序(比如按
qId顺序),不能并行调用downloadItems,而是要串行处理每个文档,比如用递归或者设置OperationQueue的最大并发数为1。 - Realm的写入操作尽量放在专用串行队列中,避免多线程写入冲突,当前的
try! self.realm.write如果在多线程调用可能会出问题。
内容的提问来源于stack exchange,提问作者Captain725
相关产品推荐
相关产品推荐

