美文网首页
Alamofire 后台下载和流程分析

Alamofire 后台下载和流程分析

作者: 盾子 | 来源:发表于2019-08-21 18:06 被阅读0次

    前言

    在开发中,为了提升用户体验常常会把下载大文件等网络请求放到后台下载,这样即使用户进入了后台,任务也能继续进行。那么这篇文章就来讨论下如何使用Apple原生框架URLSession的API和基于URLSession的第三方框架Alamofire来实现后台下载功能。

    通过URLSession实现后台下载

    一、 首先发起一个background模式的请求:

    // 初始化一个background的模式的configuration. Identifier:配置对象的唯一标识符
    let configuration = URLSessionConfiguration.background(withIdentifier: "com.test")
    // 创建session
    let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
    // 创建downloadTask任务,然后resume启动
    session.downloadTask(with: URL(string: urlStr).resume()
    
    • URLSessionConfiguration有三种模式,只有background 的模式才能进行后台下载。
      • default:默认模式,通常我们用这种模式就足够了。default 模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书。
      • ephemeral:系统没有任何持久性存储,所有内容的生命周期都与 session 相同,当 session 无效时,所有内容自动释放。
      • background: 创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的会话。
    • background 模式与 default 模式非常相似,只不过 background 模式会用一个独立线程来进行数据传输。background 模式可以在程序挂起,退出,崩溃的情况下运行 task,也可以利用标识符来恢复进。注意,后台 Session 一定要是唯一的 identifier ,这样在 APP 下次运行的时候,能够根据 identifier 来进行相关的区分。如果用户关闭了 APP , iOS 系统会关闭所有的background Session。而且,被用户强制关闭了以后,iOS 系统不会主动唤醒 APP,只有用户下次启动了 APP,数据传输才会继续。

    二、 实现相关代理回调

    extension ViewController:URLSessionDownloadDelegate {
        // 下载完成回调
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
            let locationPath = location.path
            let documnets = NSHomeDirectory() + "/Documents/" + "\(Date().timeIntervalSince1970)" + ".mp4"
            let fileManager = FileManager.default
            //拷贝到用户目录
            try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
        }
        
        // 监听下载进度。http分片段传输,所以这个代理会回调多次
        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的两个代理方法来监听和处理下载的数据。

    三、AppDelegate中处理后台下载回调闭包

    • 当如果只实现了前面两个步骤,是无法实现后台下载功能的。这是因为漏掉了一个非常重要的步骤。
    • 首先在Appdelegate中实现handleEventsForBackgroundURLSession回调,并且保存完成block。
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        //用于保存后台下载的completionHandler
        var backgroundSessionCompletionHandler: (() -> Void)?
        func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
            self.backgroundSessionCompletionHandler = completionHandler
        }
    }
    
    • 然后需要实现URLSessionDownloadDelegate代理的urlSessionDidFinishEvents方法,并且在主线程中执行在AppDelegate中保存的block。注意线程切换主线程,因为会刷新界面。
    extension ViewController:URLSessionDownloadDelegate {
        func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
            print("后台任务下载回来")
            DispatchQueue.main.async {
                guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
                backgroundHandle()
            }
        }
    }
    
    • 完成如上几个步骤就可以实现后台下载功能。
    • 应用程序在所有与URLSession对象关联的后台传输完成后调用此方法,无论传输成功完成还是导致错误。
    • 保存handleEventsForBackgroundURLSession方法的completionHandler回调,这是非常重要的。告诉系统后台下载回来及时刷新屏幕。
    • 在切到后台之后,URLSessionDownloadDelegate 的代理方法不会再收到 Task 相关的消息。当所有 Task 全都完成后,系统才会调用 AppDelegateapplication:handleEventsForBackgroundURLSession:completionHandler:回调。
    • 如果在urlSessionDidFinishEvents这个代理方法中不执行保存在AppDelegate里面的blcok,会导致刚进入前台时页面卡顿,影响用户体验,同时还会打印警告信息。

    通过Alamofire实现后台下载

    一、创建一个下载任务

    BackgroundManger.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)")
    }
    
    • 这里封装了一个专门用来后台下载管理类BackgroundManger,它是一个单例。如果你配置出来不做成单利,或者不被持有。那么会在进入后台后就会释放,网络也就会报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
    • 单例方便直接接收在AppDelegate中的application:handleEventsForBackgroundURLSession:completionHandler:方法的回调
    • Alamofire框架使用链式编程,写起来非常的方便简洁,逻辑清晰,可读性强。
    struct BackgroundManger {
        static let shared = BackgroundManger()
        let manager: SessionManager = {
            let configuration = URLSessionConfiguration.background(withIdentifier: "com.text.demo")
            configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
            return SessionManager(configuration: configuration)
        }()
    }
    

    二、处理AppDelegate中的回调

    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        BackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
    }
    
    • 这一步的作用跟用URLSession实现后台下载时的作用一样。这行代码非常重要,一定要加上,不然下载完成后进入前台会有掉帧的情况,影响用户体验。

    三、SessionManger流程分析
    Alamofire使用起来非常简单,那么它内部是怎样帮我们处理一些繁琐的事情的呢,下面一起来分析下:

    • 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)
    }
    
    • 首先通过外界传进来的configuration初始化了session对象。
    • 对代理进行移交,通过创建 SessionDelegate 这个专门处理代理的类来实现 URLSession的代理。这样可以达到业务下沉,便于阅读和解耦,每个类只需负责自己的任务,分工明确,不至于臃肿。
    • SessionDelegate 类中实现的代理有URLSessionDelegate``URLSessionTaskDelegate``URLSessionDataDelegate``URLSessionDownloadDelegate``URLSessionStreamDelegate
    • 我们知道,当后台下载任务完成后回回调urlSessionDidFinishEvents这个代理方法
    open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        sessionDidFinishEventsForBackgroundURLSession?(session)
    }
    
    • 执行了 sessionDidFinishEventsForBackgroundURLSession 闭包,那么这个闭包在什么时候赋值的呢?因为SessionDelegate这个类是专门处理代理的类,不处理其他逻辑,所以这个block应该是管理类SessionManger来处理的。这是一种非常重要的设计思维。
    • 经过查找发现在SessionManger初始化方法里面有一个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?() }
        }
    }
    
    • 这里就是代理的delegate.sessionDidFinishEventsForBackgroundURLSession闭包的声明,只要后台下载完成就会执行这个闭包
    • 闭包内部又在主线程调用了 backgroundCompletionHandler , 这是 SessionManger 对外提供的功能。这个闭包就是在AppDelegate里面handleEventsForBackgroundURLSession代理中保存的闭包
    • 流程总结:
      • 首先在AppDelegatehandleEventsForBackgroundURLSession方法里,把回调闭包completionHandler传给了 SessionManagerbackgroundCompletionHandler保存下来。
      • 当下载完成回来的时候 SessionDelegateurlSessionDidFinishEvents代理会调用,然后执行 sessionDidFinishEventsForBackgroundURLSession 闭包
      • sessionDidFinishEventsForBackgroundURLSession 闭包里面会在主线程执行SessionManagerbackgroundCompletionHandler闭包,这个闭包就是 AppDelegatecompletionHandler 闭包。

    总结

    Alamofire是对URLSession进行封装,所以这两种方式进行后台下载,原理是一样的。只是 Alamofire 使用更加简洁方便,依赖下沉,网络层下沉。在自己写sdk或者项目的时候也可以参照这种设计思想去实现。

    有问题或者建议和意见,欢迎大家评论或者私信。
    喜欢的朋友可以点下关注和喜欢,后续会持续更新文章。

    相关文章

      网友评论

          本文标题:Alamofire 后台下载和流程分析

          本文链接:https://www.haomeiwen.com/subject/essbsctx.html