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 } } }
遇到的问题
- 下载任务内部出错时,无法终止外部的
myloop循环,提示Cannot find label 'myloop' in scope - 循环中无法等待当前任务完成再执行下一步,使用
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
相关产品推荐
相关产品推荐

