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

Swift中URLSessionDataTask循环下载的终止与等待问题

多文件顺序下载问题解决(Swift 5)

需求说明

  • 按顺序下载多个文件,完成一个再启动下一个
  • 任意下载出错则终止全部流程,调用函数显示错误
  • 无错误完成后触发回调,全程运行在原后台线程

现有代码(Swift 5, Xcode 14.2)

private func fileDownload(fileNames fns:[String]) {
    if !errorBool {
        print("Main thread: \(Thread.isMainThread)")
        let group = DispatchGroup()
        
        myloop:
        for f in fns {
            let url = getURL(f)
            
            group.enter()
            
            //DispatchQueue.global(qos: .background).async {
            let task = session.dataTask(with: url) {(data, response, error) in
                defer { group.leave() }
                print("Starting task!")
                
                if error != nil && data == nil {
                    self.errorBool = true
                    break myloop //TODO 1: "Cannot find label 'myloop' in scope", "self." doesn't help
                }
                
                if let httpResponse = response as? HTTPURLResponse {
                    //Do stuff with downloaded data, more error handling that sets the error flag
                }
            }
            task.resume()
            //}
            
            //TODO 2: How do I wait here for the task to finish?
            //group.wait()
            if errorBool {
                break myloop
            }
        }
        
        group.notify(queue: .main) {
            print("Done!")
            //Displays any errors in a popup (on the main thread) through the calling function
        }
    }
}

遇到的问题

  1. 下载任务内部出错时,无法终止外部的myloop循环,提示Cannot find label 'myloop' in scope
  2. 循环中无法等待当前任务完成再执行下一步,使用group.wait()会导致死锁,切换后台线程也无效

解决方案

问题1:任务内终止外部循环

闭包是异步执行的独立作用域,无法直接访问外部循环的标签。可以通过标记变量+循环主动检测实现:

  • 保留errorBool作为实例级错误标记(需保证线程安全,见下文补充)
  • 删除闭包里的break myloop,出错时直接设置errorBool = true
  • 外部循环每次迭代前先检查errorBool,为true则主动break终止循环

问题2:实现顺序下载(等待任务完成再启动下一个)

group.wait()会阻塞当前线程,而URLSessionDataTask依赖当前线程的RunLoop执行,阻塞后任务无法启动,直接导致死锁。正确的顺序下载方式是用递归调用替代循环+等待:

重写后的代码如下:

private func fileDownload(fileNames fns: [String], index: Int = 0) {
    guard !errorBool, index < fns.count else {
        // 全部完成或出错,触发最终回调
        DispatchQueue.main.async {
            print("Done!")
            // 调用显示错误/完成状态的函数
        }
        return
    }
    
    let fileName = fns[index]
    let url = getURL(fileName)
    
    let task = session.dataTask(with: url) { [weak self] data, response, error in
        guard let self = self else { return }
        
        // 基础错误判断
        if let error = error, data == nil {
            self.errorBool = true
            self.fileDownload(fileNames: fns, index: fns.count) // 终止下载流程
            return
        }
        
        // 处理HTTP响应与下载数据
        if let httpResponse = response as? HTTPURLResponse {
            // 这里编写数据处理逻辑(如保存文件)
            // 若处理过程中出错,同样设置self.errorBool = true
        }
        
        // 无错误则启动下一个下载,出错则终止
        if !self.errorBool {
            self.fileDownload(fileNames: fns, index: index + 1)
        } else {
            self.fileDownload(fileNames: fns, index: fns.count)
        }
    }
    task.resume()
}

代码说明

  • 用递归代替循环,每次任务完成后调用自身启动下一个下载
  • 通过index参数跟踪当前下载的文件位置
  • 异步任务闭包内判断错误状态,出错则终止递归,否则继续
  • 全程保持在后台线程执行,仅最终UI回调切换到主线程
  • 用[weak self]避免循环引用

线程安全补充

errorBool会被多线程访问,需保证线程安全,可通过串行队列包裹实现:

private let errorQueue = DispatchQueue(label: "com.yourapp.errorQueue")
private var _errorBool = false

private var errorBool: Bool {
    get { errorQueue.sync { _errorBool } }
    set { errorQueue.sync { _errorBool = newValue } }
}

内容的提问来源于stack exchange,提问作者Neph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:35:41