Alamofire(2)— 後臺下載

😊😊😊Alamofire專題目錄,歡迎及時反饋交流 😊😊😊swift


Alamofire 目錄直通車 --- 和諧學習,不急不躁!api


這一篇主要講解後臺下載,後臺下載對於應用程序來講,是一個很是重要也比較好用的功能。雖然用好後臺下載的確可以大大提高用戶體驗,可是又不少時候咱們也會遇到不少坑點以及疑惑點。其中會經過 URLSessionAlamofire 兩種形式分別展開討論,對比學習才能更能體會 Alamofire 的設計思惟。Alamofire持續更新中,但願你們但願!安全

1、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 的模式的 configurationconfiguration 三種模式 ,只有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")
    }
}
複製代碼
  • 實現了 URLSessionDownloadDelegatedidFinishDownloadingTo代理,實現下載完成轉移臨時文件裏的數據到相應沙盒保存
  • 經過urlSession(_ session: downloadTask:didWriteData bytesWritten: totalBytesWritten: totalBytesExpectedToWrite: ) 的代理監聽下載進度
  • 這裏也是由於 http的分片傳輸 才致使的進度有段的感受,其實證實內部也是對這個代理方法不斷調用,才能進度回調!

這裏實現了下載功能,可是對於咱們須要的後臺下載還差一段session

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.閉包

蘋果爸爸老是能在合適時間給你優秀的建議,閱讀文檔的能力決定你是否可以在這個時代站穩本身的腳尖app

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的回調函數執行
  • 注意線程切換主線程,畢竟刷新界面

那麼若是不實現這個代理裏面的回調函數的執行,那麼會發生什麼呢async

  • 後臺下載的能力是不會影響的
  • 可是會爆出很是驗證界面刷新卡頓,影響用戶體驗
  • 同時打印臺會爆出警告
Warning: Application delegate received call to -
application:handleEventsForBackgroundURLSession:completionHandler: 
but the completion handler was never called.
複製代碼

2、Alamofire後臺下載

Alamofire框架仍是比較有感受的,這個節奏也是函數式回調,還支持鏈式請求和響應!事務邏輯很是清晰,還有代碼可讀性也是很是簡潔ide

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
}
複製代碼

3、SessionManger流程分析

一篇優秀的博客,畢竟還要跟你們交代這樣清晰的代碼的背後流程

一、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,其中configurationdefault的模式,設置了一些基本的 SessionManager.defaultHTTPHeaders 請求頭信息
  • 代理移交,經過建立 SessionDelegate 這個專門處理代理的類來實現 URLSession的代理
二、代理完成回調

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的操做的本質了!
三、流程總結
  • 首先在 AppDelegatehandleEventsForBackgroundURLSession方法裏,把回調閉包傳給了 SessionManagerbackgroundCompletionHandler
  • 在下載完成回來的時候 SessionDelegateurlSessionDidFinishEvents代理的調用 -> sessionDidFinishEventsForBackgroundURLSession 調用
  • 而後sessionDidFinishEventsForBackgroundURLSession 執行 -> SessionManagerbackgroundCompletionHandler的執行
  • 最後致使 AppDelegatecompletionHandler 的調用

不管你是使用 URLSession 的方式,仍是 Alamofire 進行後臺下載,可是原理仍是同樣的,只是 Alamofire 使用更加達到依賴下沉,網絡層下沉,使用更簡潔,這也是不少時候咱們須要第三方框架的緣由。這一篇你估計已經感覺到了 Alamofire 的舒服,那麼若是你喜歡的話,麻煩點心,關注一下。我會持續更新一個 Alamofire 的系列專題,謝謝!

就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!

相關文章
相關標籤/搜索