Alamofire之SessionManager

1、SessionManager流程分析

在使用Alamofire发起网络请求时,咱们通常会使用 request 方法请求一个网络地址。以下所示:swift

let urlBD = "https://www.baidu.com"

Alamofire.request(urlBD).response { (response) in
    print(response)
}
复制代码

在上面👆的代码中,咱们请求了百度的地址。api

咱们来查看一下 request 的内部实现是什么样子的呢? 网络

从源码中能够看到 request 有多个参数,除了必传的 url 外都有默认值。而后返回了一个 SessionManager.default.request

小伙伴们应该知道,在iOS进行网络请求的时候,是调用的 URLSession.dataTask,以下:session

let session = URLSession.init(configuration: .default)

let task = session.dataTask(with: URL(string: urlBD)!) { (data, response, error) in
    
    print(response)
}

task.resume()
复制代码

那么为何在Alamofire中不直接使用 URLSession,而要使用 SessionManager 作一层封装呢? 闭包

SessionManagerdefault方法中,使用默认的 URLSessionConfiguration,并添加了Alamofire自身的一些参数到配置中。而后初始化 SessionManager

init 函数中,除了参数 configuration外,还有 delegate 参数。而且这个参数的默认值 SessionDelegate() 被传给 URLSession 做为其代理。经过查看源码,SessionDelegate 已经实现了 URLSessionDelegateURLSessionTaskDelegateURLSessionDataDelegateURLSessionDownloadDelegateURLSessionStreamDelegate的代理方法。app

小伙伴都知道,在直接使用 URLSession 作网络请求时,通常都会将其代理设置为当前的 ViewController,而这里将其代理移交给 SessionDelegate()的目的,就是为了便于对请求做统一处理,不用让开发者在每次发起网络请求时,都去处理其回调。async

咱们继续查看 commonInit 函数的源码: ide

commonInit 函数中, delegate.sessionManager 被设置为自身 self,而 self 实际上是持有 delegate 的。那么这里会不会形成循环引用呢?答案是不会。由于 delegatesessionManagerweak 属性。

这里将 sessionManagerdelegate 是为了 一、在 delegate 处理回调的时候能够将消息回传给 sessionManager。 二、在 delegate 处理回调时,将不属于自身的业务交给 sessionManager 统一调度处理。 三、减小 delegate 与其余业务的依赖。函数

小伙伴们还要注意这里的 sessionDidFinishEventsForBackgroundURLSession闭包 源码分析

在处理后台请求时,须要用到。

以上就是 SessionManager 的源码分析。 SessionManager 初始化完成后,就直接调用 request 方法请求数据了。此处先不赘述。

2、URLSession后台下载

小伙伴们在项目中可能会遇到后台下载的状况,可能不少小伙伴会以为这个比较难,但其实 URLSession 的后台处理仍是很简单的,下面咱们来简单实现一下。

// 初始化一个background的模式的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: "BOBackground")

// 经过configuration初始化网络下载会话
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)

// 建立下载URL,下载QQ的安装包
let downloadUrl = URL(string: "https://dldir1.qq.com/qqfile/QQforMac/QQ_V6.5.5.dmg")!

// session建立downloadTask任务
let task = session.downloadTask(with: downloadUrl)

// 启动下载任务
task.resume()
复制代码

上面的代码就是一个简单的下载QQ安装包的下载任务。

  • 由于须要使用后台下载,因此须要初始化一个 background 模式的 configuration
  • 为了显示下载进度以及将下载文件移动到指定的路径,因此须要设置代理。
  • 下载任务 downloadTask 建立后,默认是挂起状态,因此须要调用 resume() 启动下载任务。

再实现代理方法。

extension ViewController: URLSessionDownloadDelegate {
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        
        // 下载完成 - 开始沙盒迁移
        print("下载完成 - \(location)")
        let locationPath = location.path
        //拷贝到用户目录
        let documnets = NSHomeDirectory() + "/Documents/" + "QQ" + ".dmg"
        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)\n 已下载: \(totalBytesWritten)\n 预计需下载: \(totalBytesExpectedToWrite)")
        
        print("下载进度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
    }
    
}
复制代码

实现上面两个协议方法来显示下载进度,以及将文件下载到指定路径。

咱们就实现了后台下载了吗?咱们是实际运行一下。发现确实能够下载了,可是当APP进入后台后,下载就中止了,并无实现后台下载。

通过一番资料查找(苹果官方文档),告诉咱们,还有最后一个步没有实现。

首先咱们须要在 AppDelegate

经过 UIApplicationDelegate 协议方法获取到 completionHandler 闭包保存起来。

ViewController 中,经过实现协议方法 urlSessionDidFinishEvents(forBackgroundURLSession:),执行这个闭包

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    
    print("后台任务下载回来")
    
    DispatchQueue.main.async {
        
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
            let backgroundHandle = appDelegate.backgroundCompletionHandler else { return }
        
        backgroundHandle()
    }
}
复制代码

添加了以上的代码后,再运行就能够实现后台下载了。

3、Alamofire后台下载

在上一节中,咱们实现了 URLSession 的简单后台下载,那么在 Alamofire 中又该如何实现后台下载呢?

小伙伴们可能会仿照第一节中进行网络请求那样,调用 Alamofire.download 方法,进行下载。运行代码彷佛也能够下载,可是却不能后台下载。

查看其源码,Alamofire.download 调用的是 SessionManager.defaultdownload 方法。在第一节中已经分析过了,SessionManager.default 使用的是 URLSessionConfiguration.default 默认配置。可是在 URLSession 的后台下载应该使用 URLSessionConfiguration.background

因此咱们应该自定义一个 SessionManager,而且使用 URLSessionConfiguration.background 初始化 SessionManager

struct BOBackgroundManager {
    
    static let `default` = BOBackgroundManager()
    
    let manager: SessionManager = {
       
        let configuration = URLSessionConfiguration.background(withIdentifier: "BOBackground")
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        
        return SessionManager(configuration: configuration)
    }()
}
复制代码

定义 BOBackgroundManager 单例来处理后台下载。

let downloadUrl = "https://dldir1.qq.com/qqfile/QQforMac/QQ_V6.5.5.dmg"

BOBackgroundManager.default.manager
    .download(downloadUrl, to: { (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])
    })
    .downloadProgress { (progress) in
        print("下载进度:\(progress)")
}
复制代码

而后使用 BOBackgroundManager.default.manager 开启下载,如上代码。

可是若是须要后台下载,还须要处理在 AppDelegate 中处理 completionHandler

由于 SessionDelegate 已经实现了 func urlSessionDidFinishEvents(forBackgroundURLSession:) 方法。

而且会调用 SessionDelegate.sessionDidFinishEventsForBackgroundURLSession 闭包。

而在第一节分析 SessionManagercommonInit 函数时,已经知道会设置 SessionDelegate.sessionDidFinishEventsForBackgroundURLSession 闭包。并在闭包内部执行 SessionManager.backgroundCompletionHandler 闭包。因此咱们只须要在 AppDelegate 中将获取到的 completionHandler 闭包,保存在 SessionManager.backgroundCompletionHandler 便可。

// 经过该方法获取到completionHandler
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    
    BOBackgroundManager.default.manager.backgroundCompletionHandler = {
        print("下载完成了")
        
        completionHandler()
    }
}
复制代码

为了方便调试,因此添加 print 函数。

通过以上的设置,便可使用 Alamofire 实现后台下载。

本篇名字虽然是Alamofire之SessionManager,可是更多的在记录后台下载的实现。也算是对本身平时项目中所用的一种总结吧。如有不足之处,请评论指正。

相关文章
相关标签/搜索