URLSession
是一個能夠響應發送或者接受HTTP請求的關鍵類。首先使用全局的 URLSession.shared
和 downloadTask
來建立一個簡單的下載任務:api
let url = URL(string: "https://mobileappsuat.pwchk.com/MobileAppsManage/UploadFiles/20190719144725271.png")
let request = URLRequest(url: url!)
let session = URLSession.shared
let downloadTask = session.downloadTask(with: request,
completionHandler: { (location:URL?, response:URLResponse?, error:Error?)
-> Void in
print("location:\(location)")
let locationPath = location!.path
let documnets:String = NSHomeDirectory() + "/Documents/1.png"
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
print("new location:\(documnets)")
})
downloadTask.resume()
複製代碼
能夠看到這裏的下載是前臺下載,也就是說若是程序退到後臺(好比按下 home
鍵、或者切到其它應用程序上),當前的下載任務便會馬上中止,這個樣話對於一些較大的文件,下載過程當中用戶沒法切換到後臺,對用戶來講是一種不太友好的體驗。下面來看一下在後臺下載的具體實現:緩存
咱們能夠經過URLSessionConfiguration
類新建URLSession
實例,而URLSessionConfiguration
這個類是有三種模式的: bash
URLSessionConfiguration
的三種模以下式:session
default
:默認會話模式(使用的是基於磁盤緩存的持久化策略,一般使用最多的也是這種模式,在default
模式下系統會建立一個持久化的緩存並在用戶的鑰匙串中存儲證書)ephemeral
:暫時會話模式(該模式不使用磁盤保存任何數據。而是保存在 RAM
中,全部內容的生命週期都與session
相同,所以當session
會話無效時,這些緩存的數據就會被自動清空。)background
:後臺會話模式(該模式能夠在後臺完成上傳和下載。)注意:
background
模式與default
模式很是類似,不過background
模式會用一個獨立線程來進行數據傳輸。background
模式能夠在程序掛起,退出,崩潰的狀況下運行task
。也能夠在APP
下次啓動的時候,利用標識符來恢復下載。閉包
下面先來建立一個後臺下載的任務background
session
,而且指定一個 identifier
:app
let urlstring = URL(string: "https://dldir1.qq.com/qqfile/QQforMac/QQ_V6.5.5.dmg")!
// 第一步:初始化一個background後臺模式的會話配置configuration
let configuration = URLSessionConfiguration.background(withIdentifier: "com.Henry.cn")
// 第二步:根據配置的configuration,初始化一個session會話
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 第三步:傳入URL,建立downloadTask下載任務,開始下載
session.downloadTask(with: url).resume()
複製代碼
接下來實現session
的下載代理URLSessionDownloadDelegate
,URLSessionDelegate
的方法:async
extension ViewController:URLSessionDownloadDelegate{
// 下載代理方法,下載結束
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// 下載完成 - 開始沙盒遷移
print("下載完成地址 - \(location)")
let locationPath = location.path
//拷貝到用戶目錄
let documnets = NSHomeDirectory() + "/Documents/" + "com.Henry.cn" + ".dmg"
print("移動到新地址:\(documnets)")
//建立文件管理器
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
}
//下載代理方法,監聽下載進度
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print(" bytesWritten \(bytesWritten)\n totalBytesWritten \(totalBytesWritten)\n totalBytesExpectedToWrite \(totalBytesExpectedToWrite)")
print("下載進度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
}
}
複製代碼
設置完這些代碼以後,還不能達到後臺下載的目的,還須要在AppDelegate
中開啓後臺下載的權限,實現handleEventsForBackgroundURLSession
方法:ide
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//用於保存後臺下載的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
self.backgroundSessionCompletionHandler = completionHandler
}
}
複製代碼
實現到這裏已基本實現了後臺下載的功能,在應用程序切換到後臺以後,session
會和 ApplicationDelegate
作交互,session
中的task
還會繼續下載,當全部的task
完成以後(不管下載失敗仍是成功),系統都會調用ApplicationDelegate
的application:handleEventsForBackgroundURLSession:completionHandler:
回調,在處理事件以後,在 completionHandler
參數中執行 閉包,這樣應用程序就能夠獲取用戶界面的刷新。ui
若是咱們查看handleEventsForBackgroundURLSession
這個api
的話,會發現蘋果文檔要求在實現下載完成後須要實現URLSessionDidFinishEvents
的代理,以達到更新屏幕的目的。url
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("後臺任務")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
複製代碼
若是沒有實現此方法的話⚠️️:後臺下載的實現是不會有影響的,只是在應用程序由後臺切換到前臺的過程當中可能會形成卡頓或者掉幀,同時可能在控制檯輸出警告:
經過上面的例子🌰會發現若是要實現一個後臺下載,須要寫不少的代碼,同時還要注意後臺下載權限的開啓,完成下載以後回調的實現,漏掉了任何一步,後臺下載都不可能完美的實現,下面就來對比一下,在Alamofire
中是怎麼實現後臺下載的。
首先先建立一個ZHBackgroundManger
的後臺下載管理類:
struct ZHBackgroundManger {
static let shared = ZHBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.Henry.AlamofireDemo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
configuration.sharedContainerIdentifier = "com.Henry.AlamofireDemo"
return SessionManager(configuration: configuration)
}()
}
複製代碼
後臺下載的實現:
ZHBackgroundManger.shared.manager
.download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileUrl = documentUrl?.appendingPathComponent(response.suggestedFilename!)
return (fileUrl!,[.removePreviousFile,.createIntermediateDirectories])
}
.response { (downloadResponse) in
print("下載回調信息: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("下載進度 : \(progress)")
}
複製代碼
並在AppDelegate
作統一的處理:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
ZHBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
複製代碼
這裏可能會有疑問🤔,爲甚麼要建立一個ZHBackgroundManger
單例類?
那麼下面就帶着這個疑問❓來探究一下
若是點擊ZHBackgroundManger.shared.manager.download
這裏的manager
會發現這是SessionManager
,那麼就跟進去SessionManager
的源碼來看一下:
SessionManager
的
default
方法中,是對
URLSessionConfiguration
作了一些配置,並初始化
SessionManager
.
那麼再來看SessionManager
的初始化方法:
SessionManager
的
init
初始化方法中,能夠看到這裏把
URLSessionConfiguration
設置成
default
模式,
在內容的前篇,在建立一個URLSession
的後臺下載的時候,咱們已經知道須要把URLSessionConfiguration
設置成background
模式才能夠。
在初始化方法裏還有一個SessionDelegate
的delegate
,並且這個delegate
被傳入到URLSession
中做爲其代理,而且session
的這個初始化也就使得之後的回調都將會由 self.delegate
來處理了。也就是SessionManager
實例建立一個SessionDelegate
對象來處理底層URLSession
生成的不一樣類型的代理回調。(這又稱爲代理移交)。
代理移交以後,在commonInit()
的方法中會作另外的一些配置信息:
delegate.sessionManager
被設置爲自身
self
,而
self
實際上是持有
delegate
的。並且
delegate
的
sessionManager
是
weak
屬性修飾符。
這裏這麼寫delegate.sessionManager = self
的緣由是
delegate
在處理回調的時候能夠和sessionManager
進行通訊delegate
將不屬於本身的回調處理從新交給sessionManager
進行再次分發- 減小與其餘邏輯內容的依賴
並且這裏的delegate.sessionDidFinishEventsForBackgroundURLSession
閉包,只要後臺任務下載完成就會回調到這個閉包內部,在閉包內部,回調了主線程,調用了 backgroundCompletionHandler
,這也就是在AppDelegate
中application:handleEventsForBackgroundURLSession
方法中的completionHandler
。至此,SessionManager
的流程大概就是這樣。
對於上面的疑問:
SessionManager
在設置URLSessionConfiguration
的默認的是default
模式,由於須要後臺下載的話,就須要把URLSessionConfiguration
的模式修改成background
模式。包括咱們也能夠修改URLSessionConfiguration
其餘的配置Error Domain=NSURLErrorDomain Code=-999 "cancelled"
SessionManager
從新包裝成一個單例後,在AppDelegate
中的代理方法中能夠直接使用。AppDelegate
的application:handleEventsForBackgroundURLSession
的方法裏,把回調閉包completionHandler
傳給了 SessionManager
的 backgroundCompletionHandler
.SessionDelegate
的 urlSessionDidFinishEvents
代理的調用會觸發 SessionManager
的sessionDidFinishEventsForBackgroundURLSession
代理的調用sessionDidFinishEventsForBackgroundURLSession
執行SessionManager
的 backgroundCompletionHandler
的閉包.AppDelegate
的application:handleEventsForBackgroundURLSession
的方法裏的 completionHandler
的調用.關於Alamofire
後臺下載的代碼就分析到這裏,其實經過源碼發現,和利用URLSession
進行後臺下載原理是大體相同的,只不過利用Alamofire
使代碼看起來更加簡介,並且Alamofire
中會有不少默認的配置,咱們只須要修改須要的配置項便可。