咔咔咔,敲完一个Alamofire
的下载实现:
func downLoadFile() {
SessionManager.default.download(urlString) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let docUrl = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first
let fileUrl = docUrl?.appendingPathComponent(response.suggestedFilename!)
return (fileUrl!, [.removePreviousFile, .createIntermediateDirectories])
}.downloadProgress { (progress) in
print("\(progress)")
}.response { (respond) in
print("\(respond)")
}
}
切到后台时,下载不继续执行,切回后,下载继续执行,后台下载的目的没有达到啊。。。
一通常规操作,目的没有达到啊,为什么?肯定哪里忽略了,default
有木有很刺眼?看下呗
public static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
- 是
SessionManager
的一个单例 -
URLSessionConfiguration
是default
模式 - 后台下载需要的模式是
background
再看下SessionManager
的init
方法
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
的默认值还是URLSessionConfiguration
的defualt
模式
这个时候我们就需要重新配置为background
模式了:
func downLoadBackground() {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.zimi")
let backgroundManager = SessionManager(configuration: configuration)
backgroundManager.download(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!, [.createIntermediateDirectories, .removePreviousFile])
}
.response { (response) in
print("\(response)")
}.downloadProgress { (progress) in
print("\(progress.fractionCompleted)")
}
}
// 控制台打印:<1> load failed with error Error Domain=NSURLErrorDomain Code=-999 "cancelled"
居然报错了。。。
原来是
SessionManager
在downLoadBackground
方法中是局部变量,进入后台下载时被释放了改成这样
let backgroundManager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.zimi")
let sessionManager = SessionManager(configuration: configuration)
return sessionManager
}()
URLSession
的官方文档关于后台下载有四步,在Swift - 网络 URLSession中有介绍,当然不能忘了这个重要的步骤了:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
BackgroundDLViewModel().backgroundManager.backgroundCompletionHandler = completionHandler
}
不然在控制台会打印一个警告,切回的时候也会出下卡顿
Warning: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler:
but the completion handler was never called.
那么重点来了,在用URLSession
来处理后台下载的时候,需要通过urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
代理方法来执行completionHandler
的回调,那么,既然SessionManager
的属性backgroundCompletionHandler
帮我们保存了completionHandler
这个闭包,它是怎么帮我们来调用的呢?
在前面贴出的init
方法中有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?() }
}
}
-
sessionDidFinishEventsForBackgroundURLSession
代理的闭包声明里,做了backgroundCompletionHandler
闭包回到主线程异步的回调 -
Alamofire
中有一个专职delegate
的类SessionDelegate
,对URLSession
的代理方法urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
进行了实现
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
-
SessionDelegate
重写了NSObject
的responds
方法,通过sessionDidFinishEventsForBackgroundURLSession
闭包是否为空来判断是否执行urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
方法
open override func responds(to selector: Selector) -> Bool {
#if !os(macOS)
if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) {
return sessionDidFinishEventsForBackgroundURLSession != nil
}
#endif
//省略了一些代码
}
是不是很6啊?不用我们再写代理,也不用再写代理方法的实现了,Alamofire
帮我们省了这一步了。
像
Alamofire
这些优秀的框架能帮我们省很多的代码,但我们也不能忘了原生API
的基础哦