Alamofire實現後臺下載

咔咔咔,敲完一個Alamofire的下載實現:api

func downLoadFile() {
    SessionManager.default.download(urlString) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
        let docUrl = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first
        let fileUrl = docUrl?.appendingPathComponent(response.suggestedFilename!)
        return (fileUrl!, [.removePreviousFile, .createIntermediateDirectories])
        }.downloadProgress { (progress) in
            print("\(progress)")
        }.response { (respond) in
            print("\(respond)")
    }
}
複製代碼

切到後臺時,下載不繼續執行,切回後,下載繼續執行,後臺下載的目的沒有達到啊。。。 一一般規操做,目的沒有達到啊,爲何?確定哪裏忽略了,default有木有很刺眼?看下唄bash

public static let `default`: SessionManager = {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
    
    return SessionManager(configuration: configuration)
}()
複製代碼
  • SessionManager的一個單例
  • URLSessionConfigurationdefault模式
  • 後臺下載須要的模式是background

再看下SessionManagerinit方法網絡

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的默認值仍是URLSessionConfigurationdefualt模式

這個時候咱們就須要從新配置爲background模式了:session

func downLoadBackground() {
    let configuration = URLSessionConfiguration.background(withIdentifier: "com.zimi")
    let backgroundManager = SessionManager(configuration: configuration)
    backgroundManager.download(urlString) { (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!, [.createIntermediateDirectories, .removePreviousFile])
    }
        .response { (response) in
            print("\(response)")
        }.downloadProgress { (progress) in
            print("\(progress.fractionCompleted)")
    }
}
// 控制檯打印:<1> load failed with error Error Domain=NSURLErrorDomain Code=-999 "cancelled"
複製代碼

竟然報錯了。。。閉包

原來是SessionManagerdownLoadBackground方法中是局部變量,進入後臺下載時被釋放了 改爲這樣app

let backgroundManager: SessionManager = {
    let configuration = URLSessionConfiguration.background(withIdentifier: "com.zimi")
    let sessionManager = SessionManager(configuration: configuration)
    
    return sessionManager
}()
複製代碼

URLSession的官方文檔關於後臺下載有四步,在Swift - 網絡 URLSession中有介紹,固然不能忘了這個重要的步驟了:框架

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    BackgroundDLViewModel().backgroundManager.backgroundCompletionHandler = completionHandler
}
複製代碼

否則在控制檯會打印一個警告,切回的時候也會出下卡頓異步

Warning: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler: 
but the completion handler was never called.
複製代碼

那麼重點來了,在用URLSession來處理後臺下載的時候,須要經過urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)代理方法來執行completionHandler的回調,那麼,既然SessionManager的屬性backgroundCompletionHandler幫咱們保存了completionHandler這個閉包,它是怎麼幫咱們來調用的呢? 在前面貼出的init方法中有commonInit這個方法的調用,那麼咱們來看下:async

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?() }
    }
}
複製代碼
  • sessionDidFinishEventsForBackgroundURLSession代理的閉包聲明裏,作了backgroundCompletionHandler閉包回到主線程異步的回調
  • Alamofire中有一個專職delegate的類SessionDelegate,對URLSession的代理方法urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)進行了實現
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    sessionDidFinishEventsForBackgroundURLSession?(session)
}
複製代碼
  • SessionDelegate重寫了NSObjectresponds方法,經過sessionDidFinishEventsForBackgroundURLSession閉包是否爲空來判斷是否執行urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)方法
open override func responds(to selector: Selector) -> Bool {
    #if !os(macOS)
        if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) {
            return sessionDidFinishEventsForBackgroundURLSession != nil
        }
    #endif
   //省略了一些代碼
}
複製代碼

是否是很6啊?不用咱們再寫代理,也不用再寫代理方法的實現了,Alamofire幫咱們省了這一步了。ide

Alamofire這些優秀的框架能幫咱們省不少的代碼,但咱們也不能忘了原生API的基礎哦

相關文章
相關標籤/搜索