
一、SessionManager流程分析
在使用Alamofire发起网络请求时,我们一般会使用 request
方法请求一个网络地址。如下所示:
let urlBD = "https://www.baidu.com"
Alamofire.request(urlBD).response { (response) in
print(response)
}
在上面👆的代码中,我们请求了百度的地址。
我们来查看一下 request
的内部实现是什么样子的呢?

request
有多个参数,除了必传的 url
外都有默认值。然后返回了一个 SessionManager.default.request
。
小伙伴们应该知道,在iOS进行网络请求的时候,是调用的 URLSession.dataTask
,如下:
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
做一层封装呢?

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

在 init
函数中,除了参数 configuration
外,还有 delegate
参数。并且这个参数的默认值 SessionDelegate()
被传给 URLSession
作为其代理。通过查看源码,SessionDelegate
已经实现了 URLSessionDelegate
、URLSessionTaskDelegate
、URLSessionDataDelegate
、URLSessionDownloadDelegate
、URLSessionStreamDelegate
的代理方法。
小伙伴都知道,在直接使用
URLSession
做网络请求时,一般都会将其代理设置为当前的 ViewController,而这里将其代理移交给SessionDelegate()
的目的,就是为了便于对请求作统一处理,不用让开发者在每次发起网络请求时,都去处理其回调。
我们继续查看 commonInit
函数的源码:

在 commonInit
函数中,delegate.sessionManager
被设置为自身 self
,而 self
其实是持有 delegate
的。那么这里会不会造成循环引用呢?答案是不会。因为 delegate
的 sessionManager
是 weak
属性。
这里将
sessionManager
给delegate
是为了
1、在delegate
处理回调的时候可以将消息回传给sessionManager
。
2、在delegate
处理回调时,将不属于自身的业务交给sessionManager
统一调度处理。
3、减少delegate
与其他业务的依赖。
小伙伴们还要注意这里的 sessionDidFinishEventsForBackgroundURLSession
闭包

在处理后台请求时,需要用到。
以上就是 SessionManager 的源码分析。 SessionManager
初始化完成后,就直接调用 request
方法请求数据了。此处先不赘述。
二、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()
}
}
添加了以上的代码后,再运行就可以实现后台下载了。
三、Alamofire后台下载
在上一节中,我们实现了 URLSession 的简单后台下载,那么在 Alamofire 中又该如何实现后台下载呢?
小伙伴们可能会仿照第一节中进行网络请求那样,调用 Alamofire.download
方法,进行下载。运行代码似乎也可以下载,但是却不能后台下载。
查看其源码,Alamofire.download
调用的是 SessionManager.default
的 download
方法。在第一节中已经分析过了,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
闭包。
而在第一节分析 SessionManager
的 commonInit
函数时,已经知道会设置 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,但是更多的在记录后台下载的实现。也算是对自己平时项目中所用的一种总结吧。若有不足之处,请评论指正。
网友评论