在使用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
作一层封装呢? 闭包
SessionManager
的
default
方法中,使用默认的
URLSessionConfiguration
,并添加了Alamofire自身的一些参数到配置中。而后初始化
SessionManager
。
在 init
函数中,除了参数 configuration
外,还有 delegate
参数。而且这个参数的默认值 SessionDelegate()
被传给 URLSession
做为其代理。经过查看源码,SessionDelegate
已经实现了 URLSessionDelegate
、URLSessionTaskDelegate
、URLSessionDataDelegate
、URLSessionDownloadDelegate
、URLSessionStreamDelegate
的代理方法。app
小伙伴都知道,在直接使用
URLSession
作网络请求时,通常都会将其代理设置为当前的 ViewController,而这里将其代理移交给SessionDelegate()
的目的,就是为了便于对请求做统一处理,不用让开发者在每次发起网络请求时,都去处理其回调。async
咱们继续查看 commonInit
函数的源码: ide
commonInit
函数中,
delegate.sessionManager
被设置为自身
self
,而
self
实际上是持有
delegate
的。那么这里会不会形成循环引用呢?答案是不会。由于
delegate
的
sessionManager
是
weak
属性。
这里将
sessionManager
给delegate
是为了 一、在delegate
处理回调的时候能够将消息回传给sessionManager
。 二、在delegate
处理回调时,将不属于自身的业务交给sessionManager
统一调度处理。 三、减小delegate
与其余业务的依赖。函数
小伙伴们还要注意这里的 sessionDidFinishEventsForBackgroundURLSession
闭包 源码分析
以上就是 SessionManager 的源码分析。 SessionManager
初始化完成后,就直接调用 request
方法请求数据了。此处先不赘述。
小伙伴们在项目中可能会遇到后台下载的状况,可能不少小伙伴会以为这个比较难,但其实 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()
}
}
复制代码
添加了以上的代码后,再运行就能够实现后台下载了。
在上一节中,咱们实现了 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,可是更多的在记录后台下载的实现。也算是对本身平时项目中所用的一种总结吧。如有不足之处,请评论指正。