iOS网络协议栈原理(二) -- URLSessionTask层
上一篇文章总结到, URLSession
是一个生产URLSessionDataTask
的工厂, 其中有几个关键内容, 会在这一节中解释:
- 在创建
task
时, 会将URLSession
实例传入, 也就是说task
可能会在内部持有URLSession
- 真正发起请求时, 需要调用
task.resume()
方法 -- 那么Task
究竟是如何工作的
1. Task
的初始化创建与HTTP Body
包装
简单归纳一下Task的初始化的关键信息:
- Task的回调方式: 关联回调方式, 是通过
Behaviour + taskIdentifier
存储在URLSession
中 _Body枚举
: 根据URLRequest
的httpBody or httpBodyStream
的成员, 在内部统一使用_Body枚举
收口, 具会分成4类.- 参考以下代码注释
open class URLSessionTask : NSObject, NSCopying {
...
// 构造 SessionTask 的 Body 可能有4种情况
enum _Body {
case none
case data(DispatchData) // 为啥用 DispatchData 是一个内部二进制容器类型, 实际会防止copy, 和传入的 request.httpBody 共用同一份内存
/// Body data is read from the given file URL
case file(URL) // 其他的 Task会使用 URL模式的 body
case stream(InputStream)
}
// 核心方法!
internal convenience init(session: URLSession, request: URLRequest, taskIdentifier: Int) {
if let bodyData = request.httpBody, !bodyData.isEmpty {
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.data(createDispatchData(bodyData)))
} else if let bodyStream = request.httpBodyStream {
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.stream(bodyStream))
} else {
// 其他场景, 没有body -> 例如使用 GET 请求时
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.none)
}
}
// 更加重要的方法
internal init(session: URLSession, request: URLRequest, taskIdentifier: Int, body: _Body?) {
// 1. task 会强引用创建它的 session, 并且在 task 结束的时候 self.session = nil
self.session = session
// 2. 将 workQueue的 targetQueue 设置成 session.workQueue
// 3. 后续有大量关于 task.state 的操作都在这个 serial queue 中完成!!!
self.workQueue = DispatchQueue.init(label: "org.swift.URLSessionTask.WorkQueue", target: session.workQueue)
// 4. 唯一 task Identifier, 使用这个唯一标记, 可以通过 URLSession 关联 CompletionHandler
self.taskIdentifier = taskIdentifier
// 5. 持有一份外部传入的 Request!!! 因为整个请求过程中, 可能遇到 HTTP Status Code = 302的情况, 需要重定向, 可能一次Task过程会发起多个请求
self.originalRequest = request // 原始的请求
// 6. 收口以后的 http body
self.knownBody = body
super.init()
// 7. 同 5, 初始化时, 当前的请求就是初始化的request (后面如果有302重定向场景, Task会创建一个新的指向重定向路径的request, 那时会修改 currentRequest)
self.currentRequest = request
// 8. 忽略...
self.progress.cancellationHandler = { [weak self] in
self?.cancel()
}
}
}
2. Task
的内部状态机
前面我们知道, 在使用Session
工厂, 创建Task
实例以后, 需要主动调用task.resume()
方法启动这个任务
!!! why???
实际上在task内部维护了一个state
!!! 并且通过一个成员变量suspendCount
来记录这个Task任务
是否在挂起状态!!!
extension URLSessionTask {
/// How many times the task has been suspended, 0 indicating a running task.
internal var suspendCount = 1
public enum State : Int {
/// The task is currently being serviced by the session
case running
case suspended // 初始是 suspended
/// The task has been told to cancel. The session will receive a URLSession:task:didCompleteWithError: message.
case canceling // 退出 ing!!!!!! 只是标记 canceling -> 但是没 completed
/// The task has completed and the session will receive no more delegate notifications
case completed
}
/// Updates the (public) state based on private / internal state.
///
/// - Note: This must be called on the `workQueue`.
internal func updateTaskState() {
func calculateState() -> URLSessionTask.State {
if suspendCount == 0 {
return .running
} else {
return .suspended
}
}
state = calculateState()
}
// 核心方法
open func resume() {
workQueue.sync {
// 忽略 canceling 和 completed
guard self.state != .canceling, self.state != .completed else {
return
}
if self.suspendCount > 0 {
self.suspendCount -= 1
}
self.updateTaskState()
if self.suspendCount == 0 {
...
// 真正的启动任务
}
}
}
// 核心方法
open func cancel() {
workQueue.sync {
let canceled = self.syncQ.sync { () -> Bool in
guard self._state == .running || self._state == .suspended else {
return true
}
self._state = .canceling
return false
}
guard !canceled else {
return
}
...
}
}
}
通过以上代码能看出task
的两个核心方法, 主要就是切换task.state
, 并且关于state
的操作都是在workQueue
中完成的:
- 初始化时,
task.state => .suspended
- 当外部调用
resume()
,task.state => .running
, 然后真正启动耗时任务, 具体的耗时任务是交给另外一个URLProtocl的实例
完成的, 具体启动任务的逻辑我们后面分析!!! - 当外部调用
cance()
,task.state => .canceling
, 表示task
已经取消, 然后去处理底层的资源清理工作 - 在
task
关联的URLProtocl的实例
的清理工作完成以后, 会调用task.state => .completed
!!!
因此, 简单来说, Task
并不是自己去执行网络请求, 而是又抽象了一层, 真正的苦力活都是交给URLProtocol
去做的!!!
另外Task
功能性不同, Apple 创建多个子类来进行差异化任务的实现, 另外, task progress
以及HTTP 302
重定向等逻辑都是在task层
实现的:
open class URLSessionDataTask: URLSessionTask {}
/*
* An URLSessionUploadTask does not currently provide any additional
* functionality over an URLSessionDataTask. All delegate messages
* that may be sent referencing an URLSessionDataTask equally apply
* to URLSessionUploadTasks.
*/
open class URLSessionUploadTask: URLSessionDataTask {}
/*
* URLSessionDownloadTask is a task that represents a download to
* local storage.
*/
open class URLSessionDownloadTask: URLSessionTask {
...
}