You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift iOS中URLSession上传过慢,如何并行执行多个任务?

并行处理Firebase图片上传的优化方案

当然可以并行执行多个URLSession任务,而且你的原始代码其实已经在尝试并行发起请求了,但几个关键细节拖慢了整体速度,同时你尝试的TaskManager方案并不适配你的场景。下面我来拆解问题并给出可行的优化方案:

先分析你当前代码的核心问题

  1. 线程安全隐患:你用count变量来给图片命名和生成字典Key,但多个并行任务会同时在主队列更新这个变量,很容易导致索引混乱、重复命名,甚至引发Firebase Storage的上传冲突,直接拖慢整体流程。
  2. DispatchGroup的逻辑没问题,但URLSession默认并发限制:URLSession.shared的默认配置对单域名并发请求数有一定限制(通常4-6个),你的6张图刚好卡在这个阈值边缘,可能出现部分请求等待的情况。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:24:44