Alamofire实现后台下载

369 阅读2分钟

咔咔咔,敲完一个Alamofire的下载实现:

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有木有很刺眼?看下呗

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模式了:

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方法中是局部变量,进入后台下载时被释放了 改成这样

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这个方法的调用,那么我们来看下:

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帮我们省了这一步了。

Alamofire这些优秀的框架能帮我们省很多的代码,但我们也不能忘了原生API的基础哦