iOS网络协议栈原理(三) -- 给URLSessionTask干脏活累活的URLProtocol
请求真正的发起者是URLProtocol实例
我们在URLSessionTask的resume(),cancel(),suspend()等外部API方法中, 有大量关于self._getProtocol(){...}的操作.
这个操作, 实际上是获取一个Task关联的URLProtocol实例!!!
而这个URLProtocol实例, 并不是在Task创建阶段就创建的, 而是在特定的时间点, 创建并存储到_protocolStorage中
extension URLSessionTask {
fileprivate enum ProtocolState {
case toBeCreated
case awaitingCacheReply(Bag<(URLProtocol?) -> Void>)
case existing(URLProtocol)
case invalidated
}
fileprivate let _protocolLock = NSLock()
fileprivate var _protocolStorage: ProtocolState = .toBeCreated
func _getProtocol(_ callback: @escaping (URLProtocol?) -> Void) {
_protocolLock.lock() // Must be balanced below, before we call out ⬇
switch _protocolStorage {
case .toBeCreated:
// 1. 第一次调用时, 创建一个 URLProtocol实例
let urlProtocol = _protocolClass(task: self, cachedResponse: nil, client: nil)
// 2. 存储创建的 urlProtocol实例到 _protocolStorage 中
_protocolStorage = .existing(urlProtocol)
_protocolLock.unlock() // Balances above ⬆
// 3. 返回这个指定的 protoocl
callback(urlProtocol)
case .awaitingCacheReply(let bag):
bag.values.append(callback)
_protocolLock.unlock() // Balances above ⬆
case .existing(let urlProtocol):
// 其他的如果已经创建, 直接返回这个 实例
_protocolLock.unlock() // Balances above ⬆
callback(urlProtocol)
case .invalidated:
_protocolLock.unlock() // Balances above ⬆
callback(nil)
}
}
}
在Task创建时, _protocolStorage = .toBeCreated, 当第一次调用_getProtocol(...)方法时, 会使用_protocolClass(task: self, cachedResponse: nil, client: nil)方法创建一个 URLProtocl的实例, 并且将该实例存储到_protocolStorage = .existing(urlProtocol).
// 真实的 task.resume 启动请求!
open func resume() {
self._getProtocol { urlProtocol in
self.workQueue.async {
// 真实的启动!!! 实际是调用 _protocol.startLoading() 方法
if let _protocol = urlProtocol {
_protocol.startLoading()
} else if self.error == nil {
...
let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain,
code: NSURLErrorUnsupportedURL,
userInfo: userInfo))
self.error = urlError
_ProtocolClient().urlProtocol(task: self, didFailWithError: urlError)
}
}
}
}
open func cancel() {
...
guard !canceled else {
return
}
self._getProtocol { urlProtocol in
self.workQueue.async {
...
let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: info))
self.error = urlError
if let urlProtocol = urlProtocol {
urlProtocol.stopLoading()
urlProtocol.client?.urlProtocol(urlProtocol, didFailWithError: urlError)
}
}
}
}
结合task.resume()的方法, 我们知道以下关键信息:
_getProtocol()第一次调用时, 会创建一个protocol实例, 并存储在_protocolStorage中.- 真实的请求, 是通过
_protocol实例的_protocol.startLoading()方法发起的!!! task.cancel()方法, 也是获取_protocol实例, 然后调用urlProtocol.stopLoading()结束请求
一句话总结, Task并不是自己去发起请求, 真正的请求发起和取消操作都是交给更下层的URLProtocol去完成的!!!
为了简化理解, 以上内容我们忽略了, URL Cache的内容
Task关联的URLProtocol实例的创建过程
前面我们知道, _protocolClass(task: self, cachedResponse: nil, client: nil) 会创建Task的关联URLProtocol实例, 它的核心实现如下:
extension URLSessionTask {
...
fileprivate static let registerProtocols: () = {
// TODO: We register all the native protocols here.
_ = URLProtocol.registerClass(_HTTPURLProtocol.self)
_ = URLProtocol.registerClass(_FTPURLProtocol.self)
_ = URLProtocol.registerClass(_DataURLProtocol.self)
}()
// 每个 URLSessionTask 关联一个 _protocolClass!!!
/*
会从两个地方获取:
1. 先从 session.configuration.protocolClasses 中获取, 如果没有找到
2. 再尝试从全局静态的 URLProtocol.getProtocols() 获取!!! 系统在初始化时, 会往全局 URLProtocol.prototcolClasses 中注册几个常用的 URLProtocol.Type
*/
private var _protocolClass: URLProtocol.Type {
...
let protocolClasses = session.configuration.protocolClasses ?? []
if let urlProtocolClass = URLProtocol.getProtocolClass(protocols: protocolClasses, request: request) {
guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError("A protocol class specified in the URLSessionConfiguration's .protocolClasses array was not a URLProtocol subclass: \(urlProtocolClass)") }
return urlProtocol
} else {
let protocolClasses = URLProtocol.getProtocols() ?? []
if let urlProtocolClass = URLProtocol.getProtocolClass(protocols: protocolClasses, request: request) {
guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError("A protocol class registered with URLProtocol.register… was not a URLProtocol subclass: \(urlProtocolClass)") }
return urlProtocol
}
}
fatalError("Couldn't find a protocol appropriate for request: \(request)")
}
// 内部使用!!! - 静态方法 -> 用来交给 系统判断当前的 URLProtocol 能否处理这个 request, 如果可以
// 回传给 Request, 创建一个 URLProtocol 对象, 绑定这个 request, 帮助处理 Request 的真实请求协议的过程
// 入参第一个 Protocol的来源可能有两个!!
// 1. 是 NSURLSessionConfiguration
// 2. URLProtocol._registeredProtocolClasses 静态属性 (用lock保护)
internal class func getProtocolClass(protocols: [AnyClass], request: URLRequest) -> AnyClass? {
// 注册的 URLProtocol 在全局信息返回的时候是 reverse 的, 这样保证后注册的 URLProtocol 最先可能被使用
// Registered protocols are consulted in reverse order.
// This behaviour makes the latest registered protocol to be consulted first
_classesLock.lock()
let protocolClasses = protocols
for protocolClass in protocolClasses {
let urlProtocolClass: AnyClass = protocolClass
guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() }
// 遍历主动调用, 看那个 自定义的 Protocol 能处理这个 request!!!
// 可以处理, 就返回这个 protocl.class
if urlProtocol.canInit(with: request) {
_classesLock.unlock()
return urlProtocol
}
}
_classesLock.unlock()
return nil
}
}
以上过程总结一下:
- 先找具体用哪个类型的
URLProtocl:- 先从
session.configuration.protocolClasses中获取, 如果没有找到 - 再尝试从全局静态的
URLProtocol.getProtocols()获取!!! 系统在初始化时, 会往全局URLProtocol.prototcolClasses中注册几个常用的URLProtocol.Type registerProtocols()全局注册的Protocol类型(注意顺序), 这三个都是_NativeProtocol, URLProtocol的子类!!!_HTTPURLProtocol_FTPURLProtocol_DataURLProtocol
- 就是遍历所有的
protocols找到第一个满足+ canInit(with: request) == true的urlProtocol类型
- 先从
- 具体类型确定以后, 构造方法是
init(task: self, cachedResponse: nil, client: nil)
一句话总结:
对于URLProtocol, 我们可以理解成 task 任务的代理, task真实的任务都是交给URLProtocol这个类型的代理去做的, 而在URLProtocol完成脏活累活以后, 就去调用 task的适合的方法!!!!!!