美文网首页
Alamofire-SessionManager解析

Alamofire-SessionManager解析

作者: SPIREJ | 来源:发表于2019-08-19 18:46 被阅读0次

    一、SessionManager初始化

    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)
    }
    

    我们看到SessionManager类里面的初始化方法init

    创建具有指定的“配置”“委托”“serverTrustPolicyManager”的实例。

    • configuration:用于构造托管会话的配置。
      URLSessionConfiguration.default默认情况下。
      有三种模式defaultephemeralbackground,在URLSession中介绍过这三种模式;
      还有一些其他的配置,比如requestCachePolicytimeoutIntervalForRequesttimeoutIntervalForResource等等

    • delegate:初始化会话时使用的委托。SessionDelegate()

    • serverTrustPolicyManager:用于评估所有服务器信任的服务器信任策略管理器,可选值。

    其中初始化的时候有delegate.sessionManager = self
    即:SessionDelegate.sessionManager = SessionManager,代理移交

    所以我们将来在网络请求后的代理方法全部走SessionDelegate类,里面已经封装好了网络的代理方法

    • URLSessionDelegate
    • URLSessionTaskDelegate
    • URLSessionDataDelegate
    • URLSessionDownloadDelegate
    • URLSessionStreamDelegate

    二、后台下载

    二(1). 先说URLSession是如何处理后台下载

    // 1:初始化一个background的模式的configuration
    let configuration = URLSessionConfiguration.background(withIdentifier: "com.spirej.zeYaoTechnology")
    // 2:通过configuration初始化网络下载会话
    let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
    // 3:session创建downloadTask任务-resume启动
    session.downloadTask(with: url).resume()
    
    • 初始化一个background模式的configuration。有三种模式 defaultephemeralbackground
    • 通过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的分段传输才导致的进度有段的感觉,其实证明内部也是对这个代理方法不断调用,才能进度回调!

    这里实现了下载功能,但是对于后台下载还差一段

    AppDelegate类里面设置后台下载的handleEventsForBackgroundURLSession事件,告诉URLSession相关事件正在后台处理

    class AppDelegate: UIResponder, UIApplicationDelegate {
        //用于保存后台下载的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.
    

    二(1). 先说URLSession是如何处理后台下载

    Alamofire用起来那叫一个倍儿爽!函数式回调,链式请求和响应,事物逻辑非常清晰,简洁明了。

    ZYBackgroundManger.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)")
    }
    
    • 这里的结构体ZYBackgroundManger,是封装的SessionManager下载管理类,选择background模式,用作后台下载管理,并配置参数

    为什么要做成单例?

    • 如果不做成单例或者不被持有,在进入后台就会释放了,网络也会报错误Error Domain=NSURLErrorDomain Code=-999 "cancelled"
    struct ZYBackgroundManger {    
        static let shared = ZYBackgroundManger()
    
        let manager: SessionManager = {
            let configuration = URLSessionConfiguration.background(withIdentifier: "com.zeYao.AlamofireTest.demo")
            configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
            configuration.timeoutIntervalForRequest = 10
            configuration.timeoutIntervalForResource = 10
            configuration.sharedContainerIdentifier = "group.com.zeYao.AlamofireTest"
            return SessionManager(configuration: configuration)
        }()
    }
    
    • 最后在APPDelegate类设置下载完成的回调
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        ZYBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
    }
    
    • backgroundCompletionHandlerAlamofireSessionManager下载管理类专门提供的后台下载的闭包回调
    • 看上面的解释已经说了,后台完成处理程序闭包由UIApplicationDelegate提供:handleEventsForBackgroundURLSession: completionHandler:方法。通过设置后台下载模式完成处理程序,将自动调用。
    /// The background completion handler closure provided by the UIApplicationDelegate
    /// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
    /// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
    /// will automatically call the handler.
    ///
    /// If you need to handle your own events before the handler is called, then you need to override the
    /// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
    ///
    /// `nil` by default.
    open var backgroundCompletionHandler: (() -> Void)?
    

    三、SessionManager流程分析

    SessionManager主要干了两件事:

      1. SessionManger初始化
      1. 代理移交
    1. SessionManger初始化

    SessionManger两个初始化方法唯一不同的地方就是一个是可选类型的init?

    初始化了session,其中configurationdefault的模式,设置了一些基本的 SessionManager.defaultHTTPHeaders请求头信息

    2. 代理移交

    SessionManger初始化时把处理下载的代理方法移交给SessionDelegate类,SessionDelegate集合了所有的代理。

    这里我们根据需求来到 urlSessionDidFinishEvents 的代理

    open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
            sessionDidFinishEventsForBackgroundURLSession?(session)
        }
    
    • 这里执行了 sessionDidFinishEventsForBackgroundURLSession 闭包的执行,那么这个闭包在什么时候申明的呢?

    在我们的 SessionManger 里面的初始化的时候,有一个方法commonInit

    delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
        guard let strongSelf = self else { return }
        DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
    }
    
    • 这里就是代理的delegate.sessionDidFinishEventsForBackgroundURLSession闭包的声明
    • 只要后台下载完成就会来到这个闭包内部
    • 回调了主线程,调用了backgroundCompletionHandler, 这也是 SessionManger 对外提供的功能!
    3. 流程总结
    • 首先在 AppDelegatehandleEventsForBackgroundURLSession方法里,把回调闭包传给了SessionManagerbackgroundCompletionHandler

    • 在下载完成回来的时候SessionDelegateurlSessionDidFinishEvents代理的调用 -> sessionDidFinishEventsForBackgroundURLSession闭包

    • 然后sessionDidFinishEventsForBackgroundURLSession 执行 -> SessionManagerbackgroundCompletionHandler的执行

    • 最后导致 AppDelegatecompletionHandler 的调用

    相关文章

      网友评论

          本文标题:Alamofire-SessionManager解析

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