我们先从基本的URLSession后台下载入手,对比看下Alamofire的后台下载
URLSession后台下载
///创建一个后台下载的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
///使用configuration初始化URLSession
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
///创建task任务 千万别忘了resume开启!
session.downloadTask(with: url).resume()
URLSessionConfiguration有三种模式,后台下载用到的是background
,另外两种是default
和ephemeral
。顾名思义,平时用的就是default
, 默认的URL会话配置,其存储方式是基于硬盘的持久化存储方式,会保存用户的证书到钥匙串中。ephemeral
对缓存、cookie或证书不使用持久存储。
接下来就是URLSessionDownloadDelegate代理的实现
//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")
}
}
-
didFinishDownloadingTo
在下载完成的时候会调用,一般下载的文件都存储在临时文件里面,我们要把文件保存到相应的沙盒路径就在这里操作。 -
didWriteData
这个代理方法监听下载进度。
到这里并没有完成后台下载所有步骤,翻阅官方文档可知,这里还需要实现一个APPDelegate中处理后台下载的代理方法handleEventsForBackgroundURLSession
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
代理方法,这样才可以完整的实现后台下载
注意切换主线程,UI刷新是要回来滴
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("后台任务下载回来")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
Alamofire后台下载
首先要用单利封装一下Alamofire的后台下载管理类,要考虑后台下载作用域的问题,上面URLSession
的例子就可以看出,必然要在APPDelegate
里面实现代理方法的。而且Session
不被持有的话,当进入后台的时候就被释放了,无法进行回调。
单利封装Alamofire后台下载管理类
struct AlamofireBackgroundManger {
static let shared = AlamofireBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
configuration.sharedContainerIdentifier = "group.com.AlamofireTest"
return SessionManager(configuration: configuration)
}()
}
不需要实现下载回调的代理,直接链式调用
AlamofireBackgroundManger.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)")
}
APPDelegate
里面直接用单利接收
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
AlamofireBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
Alamofire的SessionManager源码分析
1、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)
}
-
configuration
默认是default
,配置了一些请求头的基本信息,感兴趣可以抓包看看 -
delegate
代理移交,创建一个SessionDelegate()
将URLSession
的代理移交给自己实现 -
commonInit
做了什么下面讲
2、代理
源码点进去看 SessionDelegate
这个类,它集合了所有URLSession的代理
- URLSessionDelegate
- URLSessionTaskDelegate
- URLSessionDataDelegate
- URLSessionDownloadDelegate
- URLSessionStreamDelegate
还记得urlSessionDidFinishEvents
这个回调吗,进去看看
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
sessionDidFinishEventsForBackgroundURLSession
这个闭包是在哪里声明的?代理类里面是不会有逻辑数据处理的,封装设计的思路必将这种处理交给管理类下发
初始化的时候有个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
的声明,只要下载完成就会来到这个闭包内部,回到主线程调用SessionManager
对外提供的.backgroundCompletionHandler
闭包,在APPDelegate中的代理方法handleEventsForBackgroundURLSession
就是把回调传给这个闭包。
流程总结图
使用Alamofire跟URLSession进行网络请求的原理是一样的,但是Alamofire的封装使依赖和网络层下沉,使用链式请求,函数式回调让代码简洁可读性更高