上一篇文章 Alamofire之Request(一) 简单介绍了 Alamofire - Request
的整体流程. 但是有些细节是被过掉了的. 这里就详细补充一下.
上篇文章末尾我们提到了
delegate[task] = request
即在 SessionDelegate
中建立了 task
和 request
的绑定关系. 可以很方便的通过 task
获取到 request
. 那么为什么要这么做呢 ?
SessionDelegate扮演的角色
看下这张图:
图中得知:
SessionDelegate
是面向SessionManager
的协议集合,其内部实现了所有和URLSession
有关的Delegate
。那么它就有该任务交由谁去执行的决定权.
其实真正情况如下:
- 真正处理任务的是
task
对应request
的delegate
.DownloadTaskDelegate
、DataTaskDelegate
、UploadTaskDelegate
等来具体处理回调。- 有部分情况
SessionDelegate
会查看用户有没有指定, 如果用户有指定, 则还需调用用户指定的闭包.
来看个🌰:
extension SessionDelegate: URLSessionDownloadDelegate {
open func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}
}
}
这个下载 session
的回调中就根据 downloadTaskDidFinishDownloadingToURL
这个开放出去的属性有没有被用户指定来指定执行权.
SessionManager.default.delegate.downloadTaskDidFinishDownloadingToURL = {
///...
}
如果没有指定, 就下发到 DownloadTaskDelegate
具体去执行文件操作.
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
temporaryURL = location
let result = destination(location, response)
let destinationURL = result.destinationURL
let options = result.options
self.destinationURL = destinationURL
do {
if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(at: destinationURL)
}
if options.contains(.createIntermediateDirectories) {
let directory = destinationURL.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
}
try FileManager.default.moveItem(at: location, to: destinationURL)
} catch {
self.error = error
}
}
SessionDelegate
通过的任务分发功能。让处理具体事务的 delegate 去处理对应的事务,避免了其内部逻辑混乱。同时管理触发用户发送的指令. 实现业务逻辑层下沉. 这也是我们做 SDK 时非常重要的架构思想
Alamofire中队列执行保障顺序与TimeLine
Alamofire 任务执行队列
回到我们执行 request
的代码中
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
var originalRequest: URLRequest?
do {
originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return request(originalRequest, failedWith: error)
}
}
之前上一篇博客讲述 request.resume()
后因为篇幅原因并没有继续往下讲.
那我们继续, 点进去查看方法 (找 Request
的 resume
方法).
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
先来看第一句, 当没有任务, 则队列暂停挂起. ❓
别着急 , 我们先来看看这个 queue
是什么.
open class TaskDelegate: NSObject {
public let queue: OperationQueue
init(task: URLSessionTask?) {
_task = task
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
}
}
这个 queue
作为 TaskDelegate
的一个属性, 在初始化时就设置为挂起状态, 并且最大并发操作数为1.
TaskDelegate
这个角色不熟悉的可以回顾一下这个流程:
DataRequest
在SessionManager
发起request
时创建.let request = DataRequest(session: session, requestTask: >.data(originalTask, task))
- 调用了父类的
init
方法, 创建了相应的DataTaskDelegate / DownloadTaskDelegate
等等TaskDelegate
的子类, 这个类就包含了queue
, 也就是任务的具体执行队列.
这个 queue
搞清楚了,有啥用? 接下来呢? 那么我们这里就引入一下 Alamofire 中的 TimeLine
时间轴. 然后一起讲述控制队列顺序.
Alamofire -- TimeLine
先回顾一下我们写的代码:
request("https://www.baidu.com", method: .get, parameters: ["username":"lb", "password":"123456"]).response { (response) in
print("response === \(response)")
}
打印结果:
Timeline: { "Request Start Time": 588241191.681, "Initial Response Time": 588241192.039, "Request Completed Time": 588241192.089, "Serialization Completed Time": 588241192.090, "Latency": 0.357 secs, "Request Duration": 0.407 secs, "Serialization Duration": 0.001 secs, "Total Duration": 0.409 secs }
那么我们顺着这打印顺序来找一下这些时间.
- 发起时间
刚刚在 Request
的 resume
方法中,我们也看到了这句代码
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
startTime
记录了当前的绝对时间. 也就是我们的网络请求的发起时间.
-
开始收到数据时间
查找方法:SessionDelegate
->URLSessionDataDelegate
的代理方法中didReceive data
方法,然后找到具体实现者DataTaskDelegate
方法如下:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data)
} else {
mutableData.append(data)
}
let bytesReceived = Int64(data.count)
totalBytesReceived += bytesReceived
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
记录了 initialResponseTime
即 开始接收到数据时间.
-
网络请求结束时间
再来看下DataRequest
创建方法, -> 父类init
方法
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
self.session = session
switch requestTask {
case .data(let originalTask, let task):
taskDelegate = DataTaskDelegate(task: task)
self.originalTask = originalTask
case .download(let originalTask, let task):
taskDelegate = DownloadTaskDelegate(task: task)
self.originalTask = originalTask
case .upload(let originalTask, let task):
taskDelegate = UploadTaskDelegate(task: task)
self.originalTask = originalTask
case .stream(let originalTask, let task):
taskDelegate = TaskDelegate(task: task)
self.originalTask = originalTask
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
很好奇为什么是初始化 Request
拿的是请求结束时间?
初始化
DataRequest
时,向queue
队列中添加一个任务,获取当前的时间戳赋值给endTime
,这就是网络请求结束时间。
但是因为当前队列默认为挂起状态,所以不会执行里面的任务。那么这个任务在何时执行呢?
在网络请求完成回调didCompleteWithError
方法时会恢复queue
队列。
方法查找过程 : SessionDelegate
: didCompleteWithError
-> 里面分发给了 TaskDelegate
的 urlSession(session, task:, didCompleteWithError:)
方法:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
if
let downloadDelegate = self as? DownloadTaskDelegate,
let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
{
downloadDelegate.resumeData = resumeData
}
}
queue.isSuspended = false
}
}
看到这里聪明的你是不是明白了什么? 是的, 不管外界如何链式编程. 我只要控制队列的挂起和取消就可以保证按该有的顺序执行相应的任务.
- 通过计算得出的 请求周期 ,延迟, 等部分时间
public init(
requestStartTime: CFAbsoluteTime = 0.0,
initialResponseTime: CFAbsoluteTime = 0.0,
requestCompletedTime: CFAbsoluteTime = 0.0,
serializationCompletedTime: CFAbsoluteTime = 0.0)
{
self.requestStartTime = requestStartTime
self.initialResponseTime = initialResponseTime
self.requestCompletedTime = requestCompletedTime
self.serializationCompletedTime = serializationCompletedTime
self.latency = initialResponseTime - requestStartTime
self.requestDuration = requestCompletedTime - requestStartTime
self.serializationDuration = serializationCompletedTime - requestCompletedTime
self.totalDuration = serializationCompletedTime - requestStartTime
}
由计算得出的请求周期 ,延迟, 等时间, 因此它也是帮助我们测量网络请求速度的一个很方便的工具.
最后附上一张 Alamofire 架构图:
至此,
Request
完整流程 以及SessionManger
-SessionDelegate
-Request
以及其子类 -TaskDelegate
以及其子类的层级关系图是不是更清晰完整了. 整个架构层分工清晰, 业务逻辑层下沉, 又保证了用户指令的优先级. 这SDK的架构设计思想 啧啧.