當我們下載文件時,暫停,繼續(xù),取消和斷點續(xù)傳以及失敗處理都是必不可少的. 那么我們使用 Alamofire 當然必須要知道應該如何處理.
一 . 單例封裝下載管理者
import UIKit
import Alamofire
class LBDowloadManager: NSObject {
static let shared = LBDowloadManager()
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("LBDownload")
}
//MARK: - 單利方便獲取
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "LB.AlamofireDowload")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.sharedContainerIdentifier = "group.com.LB.AlamofireDowload"
let manager = SessionManager(configuration: configuration)
manager.startRequestsImmediately = true
manager.backgroundCompletionHandler = {
debugPrint("后臺完成回來了")
}
return manager
}()
開始下載
func LBDowload(_ url: URLConvertible) -> DownloadRequest {
currentDownloadRequest = LBDowloadManager.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!
}
暫停
func suspend() {
self.currentDownloadRequest?.suspend()
}
對應的是父類 Request
的 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]
)
}
繼續(xù)
func resume() {
self.currentDownloadRequest?.resume()
}
對應的是父類 Request
的 resume
方法.
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]
)
}
取消
func cancel() {
self.currentDownloadRequest?.cancel()
}
這里調用的不再是父類 Request
的 cancel
方法. 因為子類 DownloadRequest
重寫了這個方法.
/// Cancels the request.
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]
)
}
它自動保存了 resumeData
.
這個
resumeData
到底是什么, 有興趣的同學可以開始任務后暫停, 獲取一下這個resumeData
, 把其寫入到沙盒中, 然后后綴改為.plist
. 然后直接打開查看. 其實里面包含了所有信息, 包括總大小, 已下載數(shù)據(jù)情況, 下載鏈接. 等等. 也就是說我們可以根據(jù)這個resumeData
給一個新任務去繼續(xù)下載之前的任務.
那么我們在取消之后希望繼續(xù)下載, 保持以前的進度. 則需要在開始方法中增加如下處理.
func LBDowload(_ url: URLConvertible) -> DownloadRequest {
if let resumeData = LBDowloadManager.shared.currentDownloadRequest?.resumeData {
currentDownloadRequest = LBDowloadManager.shared.manager.download(resumingWith: resumeData)
}else{
currentDownloadRequest = LBDowloadManager.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!
}
即,當 DownloadRequest
有對應的恢復數(shù)據(jù)時, 則拿到這個數(shù)據(jù)繼續(xù)下載.
清理沙盒中數(shù)據(jù)
func clear() {
let filePath = NSHomeDirectory()+"/Documents/LBDownload"
if FileManager.default.fileExists(atPath: filePath) {
try! FileManager.default.removeItem(at: self.filePath)
print("清理完成")
}
}
二 . 用戶殺死APP 重新打開續(xù)傳
當我們實際在下載中關閉app, 然后重新打開操作時, 通過在 TaskDelegate
的 taskDidCompleteWithError
回調代理的斷點發(fā)現(xiàn), 它會觸發(fā).
那么你想到了什么, 是的,我們在外部自己指定回調觸發(fā)事件. 這個
request
的流程 , 啟動 -> 響應 -> 回調. 不熟悉的可以去閱讀一下
Alamofire之Request(二)和隊列執(zhí)行順序分析
Alamofire之Request(一)
那么我們在單例 manager
中, 增加一個 taskDidComplete
的監(jiān)聽閉包.
// 用戶主動關閉app 再重新進來時會觸發(fā).
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 存儲
LBDowloadManager.shared.resumeData = resumeData
print("來了")
}
}else{
print("taskDidComplete的task情況: \(task)")
}
}
這里要提一句的是 如何獲取已下載的數(shù)據(jù) ResumeData
. 是通過閉包中返回的 Error
轉換成 NSError
中的 userInfo
參數(shù)里面獲取到的.
那么拿到了 ResumeData
, 就好辦了. 在開始下載的按鈕方法中再增加一個處理
func LBDowload(_ url: URLConvertible) -> DownloadRequest {
if self.resumeData != nil {
//用戶主動關閉 再次打開續(xù)傳
currentDownloadRequest = LBDowloadManager.shared.manager.download(resumingWith: self.resumeData!)
}else{
if let resumeData = LBDowloadManager.shared.currentDownloadRequest?.resumeData {
// 用戶取消后續(xù)傳
currentDownloadRequest = LBDowloadManager.shared.manager.download(resumingWith: resumeData)
}else{
//正常開啟新任務
currentDownloadRequest = LBDowloadManager.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 , 再次打開實現(xiàn)續(xù)傳就完成了. 那么如果是應用發(fā)生閃退呢?
三 . 應用程序發(fā)生閃退續(xù)傳.
通過實驗驗證發(fā)現(xiàn)其實 URLSessionDownloadTask
在程序發(fā)生閃退時, 其實還是會繼續(xù)下載的. 也就是說, 任務不會因為閃退而終止. 那么就不存在續(xù)傳的問題了.
那么我們如何獲取到下載的文件呢?
我們同樣新增了一個回調 downloadTaskDidFinishDownloadingToURL
, 那么我們來實際操作驗證一下.
- 首先在 VC 中創(chuàng)造一個數(shù)組越界以便崩潰.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let array = [1]
print(array[2])
}
- 然后
manager
增加下載完成回調響應
manager.delegate.downloadTaskDidFinishDownloadingToURL = { (session, downloadTask, url) in
guard let response = downloadTask.response as? HTTPURLResponse else {return}
let fileUrl = LBDowloadManager.shared.filePath.appendingPathComponent(response.suggestedFilename!)
print("轉移路徑:\(fileUrl)")
do {
if FileManager.default.fileExists(atPath: fileUrl.path) {
try FileManager.default.removeItem(at: fileUrl)
}
let directory = fileUrl.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
try FileManager.default.moveItem(at: url, to: fileUrl)
print("文件移動成功")
} catch {
print("文件移動出錯了 \(error)")
}
}
從新任務開始下載, 快下載完成, 點擊屏幕, 閃退, 然后重新運行, 等待一會兒發(fā)現(xiàn):
來了! . 看下控制臺 以及沙盒文件.
- 完美. 程序
crash
獲取下載文件.