在Alamofire
中,还有一个断点续传的重要功能。swift
首先封装一个DLDowloadManager
,便于处理缓存
class DLDowloadManager: NSObject {
static let shared = DLDowloadManager()
var currentDownloadRequest: DownloadRequest?
var resumeData: Data?
var downloadTasks: Array<URLSessionDownloadTask>?
var filePath: URL{
return FileManager.default.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first!.appendingPathComponent("com.download.denglei.cn")
}
//单例方便获取
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.denglei.AlamofireDowload")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.sharedContainerIdentifier = "group.com.denglei.AlamofireDowload"
let manager = SessionManager(configuration: configuration)
manager.startRequestsImmediately = true
}
}
复制代码
这里封装一个download
方法,专门用来处理下载及断点续传的功能bash
func download(_ url: URLConvertible) -> DownloadRequest {
//没有缓存直接下载
currentDownloadRequest = DLDowloadManager.shared.manager.download(url) { [weak self](url, reponse) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let fileUrl = self?.filePath.appendingPathComponent(reponse.suggestedFilename!)
return (fileUrl!,[.removePreviousFile, .createIntermediateDirectories] )
return currentDownloadRequest!
}
复制代码
暂停下载session
func suspend() {
self.currentDownloadRequest?.suspend()
}
复制代码
父类DownloadRequest
里的suspend
方法,经过调用task.suspend
来暂停下载任务闭包
open func suspend() {
guard let task = task else { return }
task.suspend()
NotificationCenter.default.post(
name: Notification.Name.Task.DidSuspend,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
复制代码
继续下载app
func resume() {
self.currentDownloadRequest?.resume()
}
复制代码
父类DownloadRequest
里的resume
方法,还是ide
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
复制代码
取消下载post
func cancel() {
self.currentDownloadRequest?.cancel()
}
复制代码
父类DownloadRequest
里的cancel
方法,这里还有一步额外的操做,它保存了当前下载的resumeData
,便于下次恢复下载ui
open override func cancel() {
downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }
NotificationCenter.default.post(
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task as Any]
)
}
复制代码
那么咱们在取消任务后,再次开始下载,须要判断task
里的resumeData
是否存在,若是存在则继续上次下载,修改后的download
方法url
func download(_ url: URLConvertible) -> DownloadRequest {
//取消任务后继续下载
if let resumeData = DLDowloadManager.shared.currentDownloadRequest?.resumeData {
currentDownloadRequest = DLDowloadManager.shared.manager.download(resumingWith: resumeData)
}else{
//没有缓存直接下载
currentDownloadRequest = DLDowloadManager.shared.manager.download(url) { [weak self](url, reponse) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let fileUrl = self?.filePath.appendingPathComponent(reponse.suggestedFilename!)
return (fileUrl!,[.removePreviousFile, .createIntermediateDirectories] )
}
}
return currentDownloadRequest!
}
复制代码
前面写的是正常使用时的断点续传功能,还有用户主动杀死APP 和 APP出现崩溃异常退出的状况须要处理。
首先猜测,若是用户以前主动杀死APP,那么在第二次打开后会不会走APP里的某些代理方法呢?
在以前对request
的解析过程当中,知道全部系统的代理回调都会来到SessionDelegate
里的代理里,因而在Download
有关的代理方法里都打上断点。
再次用Xcode
运行APP后,发现didCompleteWithError
里的断点被执行了,说明会运行到这里
在上面的代码里,strongSelf.taskDidComplete?(session, task, error)
,咱们发现会先执行当前的代理taskDidComplete
在外界DownloadManager
里监听一下这个taskDidComplete
,把里面的resumeData
保存起来便可
// 用户kill 进来
manager.delegate.taskDidComplete = { (seesion,task, error) in
if let error = error {
print("taskDidComplete的error状况: \(error)")
if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
// resumeData 存储
DLDowloadManager.shared.resumeData = resumeData
print("来了")
}
}else{
print("taskDidComplete的task状况: \(task)")
}
}
复制代码
download
方法,多加一种判断来处理这种状况func download(_ url: URLConvertible) -> DownloadRequest {
//处理用户主动杀死APP的状况
if self.resumeData != nil {
currentDownloadRequest = DLDowloadManager.shared.manager.download(resumingWith: self.resumeData!)
}else{
//取消任务后继续下载
if let resumeData = DLDowloadManager.shared.currentDownloadRequest?.resumeData {
currentDownloadRequest = DLDowloadManager.shared.manager.download(resumingWith: resumeData)
}else{
//没有缓存直接下载
currentDownloadRequest = DLDowloadManager.shared.manager.download(url) { [weak self](url, reponse) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let fileUrl = self?.filePath.appendingPathComponent(reponse.suggestedFilename!)
return (fileUrl!,[.removePreviousFile, .createIntermediateDirectories] )
}
}
}
return currentDownloadRequest!
}
复制代码
在VC中主动制造一个崩溃的异常
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let array = [1]
print(array[2])
}
复制代码
而后再次打开APP,等待一会发现居然再次来到了上面说的回调方法didCompleteWithError
,发现error
是空的,手动去找文件的目录发现文件也是正常下载完成的
说明在APP上次崩溃退出后,再次进来会开启一个后台下载任务,继续下载上次崩溃的任务,直到下载完成
因此咱们在taskDidComplete
也能监听到下载完成, 会和用户主动杀死APP最后来到同一个回调,能够一块儿监听
// 用户kill 进来
manager.delegate.taskDidComplete = { (seesion,task, error) in
if let error = error {
print("taskDidComplete的error状况: \(error)")
if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
// resumeData 存储
DLDowloadManager.shared.resumeData = resumeData
print("来了")
}
}else{
print("taskDidComplete的task状况: \(task)")
}
}
复制代码
downloadTaskDidFinishDownloadingToURL
里也能监听到下载完成的回调内容,在这里能够根据不一样的下载完成的url作一些处理manager.delegate.downloadTaskDidFinishDownloadingToURL = { (session, downloadTask, url) in
}
复制代码
还有一个问题,咱们在这个后台下载任务里如何监听下载进度,并更新到UI上呢?
因为会走下载完成的代理方法,那么确定也会走正在下载的代理方法didWriteData
在这个代理方法里能够拿到本次下载的字节bytesWritten
,一共已经下载的字节totalBytesWritten
和一共须要下载的字节totalBytesExpectedToWrite
因此咱们在DLDowloadManager
里可以经过这个代理方法获取到下载的进度,利用这里的数据而后再在外界更新UI便可
manager.delegate.downloadTaskDidWriteData = {(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in
}
复制代码
在实现断点续传功能的整个过程当中,再一次感觉到了Alamofire
的结构和流程
SessionDelegate
DownloadTask
任务SessionDelegate
接收系统URLSession
的下载回调DownloadTaskDelegate
处理