??????Alamofire專題目錄,歡迎及時反饋交流 ??????
Alamofire 目錄直通車 --- 和諧學習,不急不躁!
這一篇主要講解后臺下載,后臺下載對于應用程序來說,是一個非常重要也比較好用的功能。雖然用好后臺下載的確能夠大大提升用戶體驗,但是又很多時候我們也會遇到很多坑點以及疑惑點。其中會通過
URLSession
和Alamofire
兩種形式分別展開討論,對比學習才能更能體會Alamofire
的設計思維。Alamofire
持續更新中,希望大家希望!
一、URLSession處理后臺下載
URLSession
在后臺處理方面還是比較簡單的。
// 1:初始化一個background的模式的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
// 2:通過configuration初始化網絡下載會話
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 3:session創建downloadTask任務-resume啟動
session.downloadTask(with: url).resume()
- 初始化一個
background
的模式的configuration
。configuration
有 三種模式 ,只有background
的模式才能進行后臺下載。 - 通過configuration初始化網絡下載會話
session
,設置相關代理,回調數據信號響應。 -
session
創建downloadTask任務
-resume
啟動 (默認狀態:suspend
) - 接下來依賴蘋果封裝的網絡處理,發起連接 - 發送相關請求 - 回調代理響應
//MARK: - session代理
extension ViewController:URLSessionDownloadDelegate{
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// 下載完成 - 開始沙盒遷移
print("下載完成 - \(location)")
let locationPath = location.path
//拷貝到用戶目錄(文件名以時間戳命名)
let documnets = NSHomeDirectory() + "/Documents/" + self.lgCurrentDataTurnString() + ".mp4"
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")
}
}
- 實現了
URLSessionDownloadDelegate
的didFinishDownloadingTo
代理,實現下載完成轉移臨時文件里的數據到相應沙盒保存 - 通過
urlSession(_ session: downloadTask:didWriteData bytesWritten: totalBytesWritten: totalBytesExpectedToWrite: )
的代理監聽下載進度 - 這里也是因為
http的分片傳輸
才導致的進度有段的感覺,其實證明內部也是對這個代理方法不斷調用,才能進度回調!
這里實現了下載功能,但是對于我們需要的后臺下載還差一段
Applications using an NSURLSession with a background configuration may be launched or resumed in the background in order to handle the completion of tasks in that session, or to handle authentication. This method will be called with the identifier of the session needing attention. Once a session has been created from a configuration object with that identifier, the session's delegate will begin receiving callbacks. If such a session has already been created (if the app is being resumed, for instance), then the delegate will start receiving callbacks without any action by the application. You should call the completionHandler as soon as you're finished handling the callbacks.
蘋果爸爸總是能在合適時間給你優秀的建議,閱讀文檔的能力決定你是否能夠在這個時代站穩自己的腳尖
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//用于保存后臺下載的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
self.backgroundSessionCompletionHandler = completionHandler
}
}
- 實現
handleEventsForBackgroundURLSession
就可以完美后臺下載 - 告訴代理與
URLSession
相關的事件正在等待處理。 - 應用程序在所有與
URLSession對象
關聯的后臺傳輸完成后調用此方法,無論傳輸成功完成還是導致錯誤。如果一個或多個傳輸需要認證,應用程序也會調用這個方法。 - 使用此方法可以重新連接任何
URLSession
并更新應用程序的用戶界面。例如,您可以使用此方法更新進度指示器或將新內容合并到視圖中。在處理事件之后,在completionHandler
參數中執行block
,這樣應用程序就可以獲取用戶界面的刷新。 - 我們通過
handleEventsForBackgroundURLSession
保存相應的回調,這也是非常必要的!告訴系統后臺下載回來及時刷新屏幕
在urlSessionDidFinishEvents
的代理實現調用
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("后臺任務下載回來")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
- 拿到
UIApplication.shared.delegate
的回調函數執行 - 注意線程切換主線程,畢竟刷新界面
那么如果不實現這個代理里面的回調函數的執行,那么會發生什么呢
- 后臺下載的能力是不會影響的
- 但是會爆出非常驗證界面刷新卡頓,影響用戶體驗
- 同時打印臺會爆出警告
Warning: Application delegate received call to -
application:handleEventsForBackgroundURLSession:completionHandler:
but the completion handler was never called.
二、Alamofire后臺下載
Alamofire
框架還是比較有感覺的,這個節奏也是函數式回調,還支持鏈式請求和響應!事務邏輯非常清晰,還有代碼可讀性也是非常簡潔
LGBackgroundManger.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)")
}
- 這里封裝了一個單利
LGBackgroundManger
的后臺下載管理類,調用manger
的手法也是非常直接。 - 封裝的思想再也不需要去處理惡心的代理事件
struct LGBackgroundManger {
static let shared = LGBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgcooci.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
configuration.sharedContainerIdentifier = "group.com.lgcooci.AlamofireTest"
return SessionManager(configuration: configuration)
}()
}
可能很多同學都在質疑為什么要做成單利,URLSession的時候不是挺好的?
- 如果你是
SessionManager.defalut
顯然是不可以的!畢竟要求后臺下載,那么我們的會話session
的配置URLSessionConfiguration
是要求background
模式的 - 如果你配置出來不做成單利,或者不被持有!在進入后臺就會釋放,網絡也就會報錯:
Error Domain=NSURLErrorDomain Code=-999 "cancelled"
- 應用層與網絡層也可以達到分離。
- 能夠幫助在
AppDelegate
的回調方便直接接收
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
LGBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
三、SessionManger流程分析
一篇優秀的博客,畢竟還要跟大家交代這樣清晰的代碼的背后流程
1、SessionManger初始化
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)
}
- 初始化了
session
,其中configuration
是default
的模式,設置了一些基本的SessionManager.defaultHTTPHeaders
請求頭信息 - 代理移交,通過創建
SessionDelegate
這個專門處理代理的類來實現URLSession
的代理
2、代理完成回調
SessionDelegate 是一個非常重要的類,集合所有的代理
- URLSessionDelegate
- URLSessionTaskDelegate
- URLSessionDataDelegate
- URLSessionDownloadDelegate
- URLSessionStreamDelegate
這里我們根據需求來到 urlSessionDidFinishEvents
的代理
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
- 這里執行了
sessionDidFinishEventsForBackgroundURLSession
閉包的執行,那么這個閉包在什么時候申明的呢? - 如果你足夠聰明,這里你應該是能夠想到的,
SessionDelegate
只是處理代理的專門類,但不是邏輯數據的處理類,按照封裝設計的常規思路必將交給管理者類來下發
在我們的 SessionManger
里面的初始化的時候,有一個方法commonInit
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
- 這里就是代理的
delegate.sessionDidFinishEventsForBackgroundURLSession
閉包的聲明 - 只要后臺下載完成就會來到這個閉包內部
- 回調了主線程,調用了
backgroundCompletionHandler
, 這也是SessionManger
對外提供的功能!聰明的你應該知道知道了我在application
的操作的本質了!
3、流程總結
- 首先在 AppDelegate的
handleEventsForBackgroundURLSession
方法里,把回調閉包傳給了 SessionManager 的backgroundCompletionHandler
- 在下載完成回來的時候 SessionDelegate 的
urlSessionDidFinishEvents
代理的調用 ->sessionDidFinishEventsForBackgroundURLSession
調用 - 然后
sessionDidFinishEventsForBackgroundURLSession
執行 -> SessionManager 的backgroundCompletionHandler
的執行 - 最后導致 AppDelegate 的
completionHandler
的調用
無論你是使用
URLSession
的方式,還是Alamofire
進行后臺下載,但是原理還是一樣的,只是Alamofire
使用更加達到依賴下沉,網絡層下沉,使用更簡潔,這也是很多時候我們需要第三方框架的原因。這一篇你估計已經感受到了Alamofire
的舒服,那么如果你喜歡的話,麻煩點心,關注一下。我會持續更新一個Alamofire
的系列專題,謝謝!就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!