Swift iOS中URLSession上传过慢,如何并行执行多个任务?
并行处理Firebase图片上传的优化方案
当然可以并行执行多个URLSession任务,而且你的原始代码其实已经在尝试并行发起请求了,但几个关键细节拖慢了整体速度,同时你尝试的TaskManager方案并不适配你的场景。下面我来拆解问题并给出可行的优化方案:
先分析你当前代码的核心问题
- 线程安全隐患:你用
count变量来给图片命名和生成字典Key,但多个并行任务会同时在主队列更新这个变量,很容易导致索引混乱、重复命名,甚至引发Firebase Storage的上传冲突,直接拖慢整体流程。 - DispatchGroup的逻辑没问题,但URLSession默认并发限制:
URLSession.shared的默认配置对单域名并发请求数有一定限制(通常4-6个),你的6张图刚好卡在这个阈值边缘,可能出现部分请求等待的情况。 - TaskManager不适用你的场景:这个类的作用是缓存重复URL的请求,避免重复下载,但你的每张图片URL都是唯一的,它不仅帮不上忙,还会让你更难追踪所有任务的完成状态。
优化后的并行上传实现
我们可以用DispatchGroup准确追踪所有「图片下载+Firebase Storage上传」的完整流程,同时用枚举索引代替count变量解决线程安全问题,还可以自定义URLSession配置提升并发数。
完整修正代码
// 假设这些变量已提前正确初始化 var urls: [URL] = [] let picUUID = UUID().uuidString var uploadDict: [String: Any] = [:] let uploadGroup = DispatchGroup() func startParallelImageUpload() { // 用枚举索引代替count,彻底避免线程安全问题 for (index, url) in urls.enumerated() { uploadGroup.enter() // 1. 并行加载图片数据 let customSession = createCustomURLSession() // 自定义Session提升并发 customSession.dataTask(with: url) { [weak self] data, response, error in guard let self = self else { self?.uploadGroup.leave() return } // 处理下载错误 if let error = error { print("图片下载失败: \(error.localizedDescription)") self.uploadGroup.leave() return } guard let imageData = data else { print("图片数据为空") self.uploadGroup.leave() return } // 2. 并行上传到Firebase Storage let picId = "\(self.picUUID)_\(index).jpg" self.uploadToFirebaseStorage(picId: picId, data: imageData, index: index) }.resume() } // 所有上传任务完成后,执行数据库更新 uploadGroup.notify(queue: .main) { [weak self] in self?.uploadDictToDatabase() } } // 自定义URLSession,提升单域名并发数 private func createCustomURLSession() -> URLSession { let config = URLSessionConfiguration.default config.httpMaximumConnectionsPerHost = 6 // 匹配你的图片数量,最大化并行 return URLSession(configuration: config) } // 上传到Firebase Storage并更新字典 private func uploadToFirebaseStorage(picId: String, data: Data, index: Int) { let picRef = storageRoot.child("pics/\(picId)") // 明确图片路径,避免冲突 picRef.putData(data, metadata: nil) { [weak self] metadata, error in guard let self = self else { return } defer { // 无论成功失败,都要标记任务完成 self.uploadGroup.leave() } if let error = error { print("Storage上传失败: \(error.localizedDescription)") return } if let downloadUrl = metadata?.downloadURL()?.absoluteString { // 用索引作为Key,确保字典更新无冲突 self.uploadDict["picId_\(index)"] = picId self.uploadDict["picUrl_\(index)"] = downloadUrl } } } // 上传字典到Firebase数据库 private func uploadDictToDatabase() { let dbRef = dbRoot.child("myRef") dbRef.updateChildValues(uploadDict) { error, ref in DispatchQueue.main.async { if let error = error { print("数据库更新失败: \(error.localizedDescription)") } else { self.displaySuccessAlert() } } } }
关键优化点
- 用枚举索引替代count:每个图片的索引是固定的,完全避免了多线程下的变量竞争问题,确保图片命名和字典Key的唯一性。
- 自定义URLSession配置:通过
httpMaximumConnectionsPerHost设置和图片数量一致的并发数,让所有请求同时发起,最大化利用网络带宽。 - 明确的DispatchGroup管理:每个「下载+上传」任务对应一次
enter(),在上传完成(无论成败)后调用leave(),确保所有任务都被准确追踪。 - Defer语句确保Group状态更新:用
defer保证无论上传成功还是失败,都不会遗漏leave()操作,避免DispatchGroup永远等待。
额外建议
如果你的图片体积较大,可以考虑在上传前进行压缩(比如降低分辨率、调整格式),这也能显著提升上传速度。
内容的提问来源于stack exchange,提问作者Lance Samaria
相关产品推荐
相关产品推荐

