Alamofire-SessionManager解析
一、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)
}
我们看到SessionManager类里面的初始化方法init
创建具有指定的“配置”
、“委托”
、“serverTrustPolicyManager”
的实例。
-
configuration:
用于构造托管会话的配置。
URLSessionConfiguration.default
默认情况下。
有三种模式default
、ephemeral
、background
,在URLSession中介绍过这三种模式;
还有一些其他的配置,比如requestCachePolicy
、timeoutIntervalForRequest
、timeoutIntervalForResource
等等 -
delegate:
初始化会话时使用的委托。SessionDelegate()
-
serverTrustPolicyManager:
用于评估所有服务器信任的服务器信任策略管理器,可选值。
其中初始化的时候有delegate.sessionManager = self
即:SessionDelegate.sessionManager = SessionManager
,代理移交
所以我们将来在网络请求后的代理方法全部走SessionDelegate
类,里面已经封装好了网络的代理方法
URLSessionDelegate
URLSessionTaskDelegate
URLSessionDataDelegate
URLSessionDownloadDelegate
URLSessionStreamDelegate
二、后台下载
二(1). 先说URLSession是如何处理后台下载
// 1:初始化一个background的模式的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: "com.spirej.zeYaoTechnology")
// 2:通过configuration初始化网络下载会话
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 3:session创建downloadTask任务-resume启动
session.downloadTask(with: url).resume()
- 初始化一个
background
模式的configuration
。有三种模式default
、ephemeral
、background
- 通过
configuration
初始化网络下载会话session
,设置相关代理,回调数据信号响应。 -
session
创建downloadTask
任务 -resume
启动(默认状态:suspend
) - 接下来依赖苹果封装的网络处理,发起连接 - 发送相关请求 - 回调代理响应
//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")
}
}
- 实现了
URLSessionDownloadDelegate
的didFinishDownloadingTo
代理,实现下载完成转移临时文件里的数据到相应沙盒保存 - 通过
urlSession(_ session: downloadTask:didWriteData bytesWritten: totalBytesWritten: totalBytesExpectedToWrite: )
的代理监听下载进度 - 这里也是因为
http的分段传输
才导致的进度有段的感觉,其实证明内部也是对这个代理方法不断调用,才能进度回调!
这里实现了下载功能,但是对于后台下载还差一段
在AppDelegate类
里面设置后台下载的handleEventsForBackgroundURLSession
事件,告诉URLSession
相关事件正在后台处理
class AppDelegate: UIResponder, UIApplicationDelegate {
//用于保存后台下载的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
self.backgroundSessionCompletionHandler = completionHandler
}
}
- 实现
handleEventsForBackgroundURLSession
就可以完美后台下载 - 告诉代理与
URLSession
相关的事件正在等待处理。 - 应用程序在所有与
URLSession对象
关联的后台传输完成后调用此方法,无论传输成功完成还是导致错误。如果一个或多个传输需要认证,应用程序也会调用这个方法。 - 使用此方法可以重新连接任何
URLSession
并更新应用程序的用户界面。例如,您可以使用此方法更新进度指示器或将新内容合并到视图中。在处理事件之后,在completionHandler
参数中执行block
,这样应用程序就可以获取用户界面的刷新。 - 我们通过
handleEventsForBackgroundURLSession
保存相应的回调,这也是非常必要的!告诉系统后台下载回来及时刷新屏幕
在urlSessionDidFinishEvents
的代理实现调用
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("后台任务下载回来")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
- 拿到
UIApplication.shared.delegate
的回调函数执行
注意线程切换主线程,毕竟刷新界面
那么如果不实现这个代理里面的回调函数的执行,那么会发生什么呢
- 后台下载的能力是不会影响的
- 但是会爆出非常验证界面刷新卡顿,影响用户体验
- 同时打印台会爆出警告
Warning: Application delegate received call to - application:handleEventsForBackgroundURLSession:completionHandler:
but the completion handler was never called.
二(1). 先说URLSession是如何处理后台下载
Alamofire
用起来那叫一个倍儿爽!函数式回调,链式请求和响应,事物逻辑非常清晰,简洁明了。
ZYBackgroundManger.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)")
}
- 这里的结构体
ZYBackgroundManger
,是封装的SessionManager
下载管理类,选择background
模式,用作后台下载管理,并配置参数
为什么要做成单例?
- 如果不做成单例或者不被持有,在进入后台就会释放了,网络也会报错误
Error Domain=NSURLErrorDomain Code=-999 "cancelled"
struct ZYBackgroundManger {
static let shared = ZYBackgroundManger()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.zeYao.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
configuration.sharedContainerIdentifier = "group.com.zeYao.AlamofireTest"
return SessionManager(configuration: configuration)
}()
}
- 最后在
APPDelegate
类设置下载完成的回调
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
ZYBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}
-
backgroundCompletionHandler
是Alamofire
的SessionManager
下载管理类专门提供的后台下载的闭包回调 - 看上面的解释已经说了,后台完成处理程序闭包由
UIApplicationDelegate
提供:handleEventsForBackgroundURLSession: completionHandler:
方法。通过设置后台下载模式完成处理程序,将自动调用。
/// The background completion handler closure provided by the UIApplicationDelegate
/// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
/// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
/// will automatically call the handler.
///
/// If you need to handle your own events before the handler is called, then you need to override the
/// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
///
/// `nil` by default.
open var backgroundCompletionHandler: (() -> Void)?
三、SessionManager流程分析
SessionManager主要干了两件事:
-
SessionManger
初始化
-
- 代理移交
1. SessionManger初始化
SessionManger
两个初始化方法唯一不同的地方就是一个是可选类型的init?
初始化了session
,其中configuration
是default
的模式,设置了一些基本的 SessionManager.defaultHTTPHeaders
请求头信息
2. 代理移交
SessionManger
初始化时把处理下载的代理方法移交给SessionDelegate
类,SessionDelegate
集合了所有的代理。
这里我们根据需求来到 urlSessionDidFinishEvents
的代理
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
- 这里执行了
sessionDidFinishEventsForBackgroundURLSession
闭包的执行,那么这个闭包在什么时候申明的呢?
在我们的 SessionManger 里面的初始化的时候,有一个方法commonInit
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
- 这里就是代理的
delegate.sessionDidFinishEventsForBackgroundURLSession
闭包的声明 - 只要后台下载完成就会来到这个闭包内部
- 回调了主线程,调用了
backgroundCompletionHandler
, 这也是SessionManger
对外提供的功能!
3. 流程总结
-
首先在
AppDelegate
的handleEventsForBackgroundURLSession
方法里,把回调闭包传给了SessionManager
的backgroundCompletionHandler
-
在下载完成回来的时候
SessionDelegate
的urlSessionDidFinishEvents
代理的调用 ->sessionDidFinishEventsForBackgroundURLSession
闭包 -
然后
sessionDidFinishEventsForBackgroundURLSession
执行 ->SessionManager
的backgroundCompletionHandler
的执行 -
最后导致
AppDelegate
的completionHandler
的调用