iOS网络协议栈原理(三) -- 给URLSessionTask干脏活累活的URLProtocol

364 阅读4分钟

iOS网络协议栈原理(三) -- 给URLSessionTask干脏活累活的URLProtocol

请求真正的发起者是URLProtocol实例

我们在URLSessionTaskresume(),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()的方法, 我们知道以下关键信息:

  1. _getProtocol()第一次调用时, 会创建一个 protocol实例, 并存储在_protocolStorage中.
  2. 真实的请求, 是通过 _protocol实例的 _protocol.startLoading()方法发起的!!!
  3. 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
    }
}

以上过程总结一下:

  1. 先找具体用哪个类型的URLProtocl:
    1. 先从 session.configuration.protocolClasses 中获取, 如果没有找到
    2. 再尝试从全局静态的 URLProtocol.getProtocols() 获取!!! 系统在初始化时, 会往全局 URLProtocol.prototcolClasses 中注册几个常用的 URLProtocol.Type
    3. registerProtocols()全局注册的Protocol类型(注意顺序), 这三个都是_NativeProtocol, URLProtocol 的子类!!!
      1. _HTTPURLProtocol
      2. _FTPURLProtocol
      3. _DataURLProtocol
    4. 就是遍历所有的protocols找到第一个满足+ canInit(with: request) == trueurlProtocol类型
  2. 具体类型确定以后, 构造方法是init(task: self, cachedResponse: nil, client: nil)

一句话总结:

对于URLProtocol, 我们可以理解成 task 任务的代理, task真实的任务都是交给URLProtocol这个类型的代理去做的, 而在URLProtocol完成脏活累活以后, 就去调用 task的适合的方法!!!!!!