我們先從基本的URLSession后臺(tái)下載入手兵琳,對比看下Alamofire的后臺(tái)下載
URLSession后臺(tái)下載
///創(chuàng)建一個(gè)后臺(tái)下載的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
///使用configuration初始化URLSession
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
///創(chuàng)建task任務(wù) 千萬別忘了resume開啟!
session.downloadTask(with: url).resume()
URLSessionConfiguration有三種模式罐氨,后臺(tái)下載用到的是background
,另外兩種是default
和ephemeral
滩援。顧名思義栅隐,平時(shí)用的就是default
, 默認(rèn)的URL會(huì)話配置,其存儲(chǔ)方式是基于硬盤的持久化存儲(chǔ)方式玩徊,會(huì)保存用戶的證書到鑰匙串中租悄。ephemeral
對緩存、cookie或證書不使用持久存儲(chǔ)恩袱。
接下來就是URLSessionDownloadDelegate代理的實(shí)現(xiàn)
//MARK: - session代理
extension ViewController: URLSessionDownloadDelegate{
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// 下載完成 - 開始沙盒遷移
print("下載完成 - \(location)")
let locationPath = location.path
//拷貝到用戶目錄(文件名以時(shí)間戳命名)
let documnets = NSHomeDirectory() + "/Documents/" + self.lgCurrentDataTurnString() + ".mp4"
print("移動(dòng)地址:\(documnets)")
//創(chuàng)建文件管理器
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("下載進(jìn)度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
}
}
-
didFinishDownloadingTo
在下載完成的時(shí)候會(huì)調(diào)用泣棋,一般下載的文件都存儲(chǔ)在臨時(shí)文件里面,我們要把文件保存到相應(yīng)的沙盒路徑就在這里操作畔塔。 -
didWriteData
這個(gè)代理方法監(jiān)聽下載進(jìn)度潭辈。
到這里并沒有完成后臺(tái)下載所有步驟,翻閱官方文檔可知俩檬,這里還需要實(shí)現(xiàn)一個(gè)APPDelegate中處理后臺(tái)下載的代理方法handleEventsForBackgroundURLSession
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//用于保存后臺(tái)下載的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
self.backgroundSessionCompletionHandler = completionHandler
}
}
URLSessionDownloadDelegate
也要實(shí)現(xiàn)urlSessionDidFinishEvents
代理方法萎胰,這樣才可以完整的實(shí)現(xiàn)后臺(tái)下載
注意切換主線程,UI刷新是要回來滴
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("后臺(tái)任務(wù)下載回來")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
Alamofire后臺(tái)下載
首先要用單利封裝一下Alamofire的后臺(tái)下載管理類棚辽,要考慮后臺(tái)下載作用域的問題技竟,上面URLSession
的例子就可以看出,必然要在APPDelegate
里面實(shí)現(xiàn)代理方法的屈藐。而且Session
不被持有的話榔组,當(dāng)進(jìn)入后臺(tái)的時(shí)候就被釋放了,無法進(jìn)行回調(diào)联逻。
單利封裝Alamofire后臺(tái)下載管理類
struct AlamofireBackgroundManger {
static let shared = AlamofireBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
configuration.sharedContainerIdentifier = "group.com.AlamofireTest"
return SessionManager(configuration: configuration)
}()
}
不需要實(shí)現(xiàn)下載回調(diào)的代理搓扯,直接鏈?zhǔn)秸{(diào)用
AlamofireBackgroundManger.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("下載回調(diào)信息: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("下載進(jìn)度 : \(progress)")
}
APPDelegate
里面直接用單利接收
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
AlamofireBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
Alamofire的SessionManager源碼分析
1、SessionManager的初始化
public init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
-
configuration
默認(rèn)是default
包归,配置了一些請求頭的基本信息锨推,感興趣可以抓包看看 -
delegate
代理移交,創(chuàng)建一個(gè)SessionDelegate()
將URLSession
的代理移交給自己實(shí)現(xiàn) -
commonInit
做了什么下面講
2公壤、代理
源碼點(diǎn)進(jìn)去看 SessionDelegate
這個(gè)類换可,它集合了所有URLSession的代理
- URLSessionDelegate
- URLSessionTaskDelegate
- URLSessionDataDelegate
- URLSessionDownloadDelegate
- URLSessionStreamDelegate
還記得urlSessionDidFinishEvents
這個(gè)回調(diào)嗎,進(jìn)去看看
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
sessionDidFinishEventsForBackgroundURLSession
這個(gè)閉包是在哪里聲明的厦幅?代理類里面是不會(huì)有邏輯數(shù)據(jù)處理的沾鳄,封裝設(shè)計(jì)的思路必將這種處理交給管理類下發(fā)
初始化的時(shí)候有個(gè)commonInit
方法是不是還沒有看?現(xiàn)在去看看
private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
session.serverTrustPolicyManager = serverTrustPolicyManager
delegate.sessionManager = self
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
}
這里有delegate.sessionDidFinishEventsForBackgroundURLSession
的聲明确憨,只要下載完成就會(huì)來到這個(gè)閉包內(nèi)部译荞,回到主線程調(diào)用SessionManager
對外提供的.backgroundCompletionHandler
閉包瓤的,在APPDelegate中的代理方法handleEventsForBackgroundURLSession
就是把回調(diào)傳給這個(gè)閉包。
流程總結(jié)圖
使用Alamofire跟URLSession進(jìn)行網(wǎng)絡(luò)請求的原理是一樣的吞歼,但是Alamofire的封裝使依賴和網(wǎng)絡(luò)層下沉圈膏,使用鏈?zhǔn)秸埱螅瘮?shù)式回調(diào)讓代碼簡潔可讀性更高