下载任务的暂停和继续在网络请求中是一个常见需求,尤其在处理大文件或者长时间下载时。在 iOS 中,URLSession
提供了支持暂停和继续下载的机制,称为 后台下载任务。
虽然上述的线程池设计可以用来调度任务并控制任务的暂停/继续,但网络请求的暂停和继续需要结合 URLSession
的 downloadTask(with:)
和 resume()
/ suspend()
方法来实现。
我们可以使用 URLSession
来创建一个下载任务,利用其支持的暂停和恢复功能,来实现下载任务的暂停和继续。
1. 使用 URLSession
实现下载暂停和继续
要支持下载任务的暂停和继续,我们需要:
- 创建一个
URLSessionDownloadTask
。 - 使用
suspend()
方法暂停下载,使用resume()
方法继续下载。 - 维护任务的状态,比如当前的下载进度,以便在恢复下载时可以从上次暂停的地方继续。
2. 示例实现
下面是一个基于 URLSession
的下载器实现,可以暂停、继续和取消下载任务:
import Foundation
/// 下载器
class ImageDownloader: NSObject, URLSessionDownloadDelegate {
private var downloadTask: URLSessionDownloadTask?
private var urlSession: URLSession!
private var resumeData: Data? // 用于恢复下载的数据
private var isDownloading = false
private var taskId: UUID?
override init() {
super.init()
// 创建 URLSession 配置并设置下载代理
let configuration = URLSessionConfiguration.default
urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
/// 开始下载图片
func startDownload(from url: URL, completion: @escaping (UIImage?) -> Void) -> UUID {
let taskId = UUID() // 为每个任务生成唯一的 ID
self.taskId = taskId
// 创建下载任务
downloadTask = urlSession.downloadTask(with: url)
// 开始下载
downloadTask?.resume()
isDownloading = true
return taskId
}
/// 暂停下载
func pauseDownload() {
downloadTask?.suspend()
isDownloading = false
}
/// 继续下载
func resumeDownload() {
if let resumeData = resumeData {
// 恢复下载任务
downloadTask = urlSession.downloadTask(withResumeData: resumeData)
downloadTask?.resume()
isDownloading = true
} else {
// 如果没有保存的暂停数据,重新开始下载
downloadTask?.resume()
isDownloading = true
}
}
/// 取消下载
func cancelDownload() {
downloadTask?.cancel()
isDownloading = false
}
// URLSessionDownloadDelegate 方法:处理下载进度和完成
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let taskId = taskId else { return }
// 下载完成后的处理
DispatchQueue.main.async {
if let data = try? Data(contentsOf: location), let image = UIImage(data: data) {
print("Download completed for task \(taskId)")
// 调用 UI 更新代码
} else {
print("Download failed for task \(taskId)")
}
}
}
// 下载进度更新
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
print("Download progress: \(progress * 100)%")
}
// 下载暂停/恢复
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
print("Download resumed at offset \(fileOffset) of total \(expectedTotalBytes) bytes")
}
// 保存暂停数据
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive resumeData: Data) {
self.resumeData = resumeData
}
}
3. 使用示例
let imageDownloader = ImageDownloader()
// 图片 URL 示例
let url = URL(string: "https://example.com/largeimage.jpg")!
// 开始下载并获取任务 ID
let taskId = imageDownloader.startDownload(from: url) { image in
if let image = image {
print("Image downloaded successfully!")
} else {
print("Failed to download image.")
}
}
// 暂停下载
imageDownloader.pauseDownload()
// 继续下载
imageDownloader.resumeDownload()
// 取消下载
imageDownloader.cancelDownload()
关键点:
-
暂停和恢复:通过
URLSessionDownloadTask
的suspend()
和resume()
方法,可以控制下载任务的暂停和恢复。resumeData
用于存储暂停点的下载数据,并且可以通过downloadTask(withResumeData:)
恢复下载任务。 -
下载进度:
URLSessionDownloadTask
提供了进度回调方法didWriteData
,我们可以通过totalBytesWritten
和totalBytesExpectedToWrite
来计算下载进度,并显示给用户。 -
任务管理:每个任务都有唯一的
taskId
,用于标识任务,以便在后续操作(暂停、继续、取消)时使用。
4. 关于暂停和继续下载的注意事项:
-
暂停和恢复时的恢复数据:
URLSessionDownloadTask
在暂停后会自动生成一个恢复数据(resumeData
),这个数据是下载文件的断点信息。你需要存储这个数据,并在恢复时重新使用downloadTask(withResumeData:)
方法。 - 恢复点的有效性:并非所有下载任务都能顺利恢复。只有部分下载任务(比如通过 HTTP 协议下载)支持断点续传,对于不支持的协议或任务,可能需要重新开始下载。
-
取消下载:通过
cancel()
方法,可以取消下载任务。取消后不会生成恢复数据,如果需要恢复,必须重新开始下载。
总结:
通过 URLSessionDownloadTask
的暂停和继续机制,我们可以很方便地在 iOS 中实现下载任务的暂停、继续和取消功能。使用 suspend()
和 resume()
方法结合 resumeData
,可以在下载中断后从上次停止的位置恢复下载,提供更好的用户体验。
结合《Swift 实现:三级下载队列》,是否可以实现优雅一点的预下载呢?
https://www.jianshu.com/p/82e88ffb1e67