iOS网络协议栈原理(四) -- 真正的干活类_NativeProtocol的启动请求的过程

251 阅读5分钟

iOS网络协议栈原理(四) -- 真正的干活类_NativeProtocol的启动请求的过程

URLProtocl的接口

前面提到真正请求是在task.resume()时, 会经历如下步骤:

  1. 构造一个[URLProtocol.self]() 数组!!! 来源是 Session.config.protocolClassesURLProtocol._registeredProtocolClasses和组合!!
  2. 从这个[URLProtocol.self]()中进行遍历,选择一个满足条件条件的URLProtocl类型,
    1. 条件: class func canInit(with task: URLSessionTask) -> Bool; 也就是判断哪个URLProtocl类型能处理这个task
  3. 选中的URLProtocl, 调用构造函数public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?), 创建实例
  4. 调用 urlprotocl.startLoading() 方法真正的启动 task 任务!!!

下面是URLProtocol的核心接口, 可以参考代码注释:

open class URLProtocol : NSObject {
    ...

    // 默认 `_HTTPURLProtocol` `_FTPURLProtocol` `_DataURLProtocol`
    private static var _registeredProtocolClasses = [AnyClass]()

    // 核心方法 -> task 底层也会去调用 canInit(with request:) 的成员方法
    // 条件: 以上第二步会调用的方法
    open class func canInit(with task: URLSessionTask) -> Bool {
        guard let request = task.currentRequest else { return false }
        return canInit(with: request)
    }
    
    // 以上第三步创建的 URLProtocol 实例!!!
    public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
        let urlRequest = task.originalRequest
        self.init(request: urlRequest!, cachedResponse: cachedResponse, client: client)
        self.task = task // URLProtocol 内部会持有上层的 task
    }

    public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
        self._request = request
        self._cachedResponse = cachedResponse
       
        self._client = client ?? _ProtocolClient()
    }

    /// 核心成员变量
    private var _task : URLSessionTask? = nil
    // 持有task.originalRequest
    private var _request : URLRequest
    // 核心成员!!! => 将来会与 task进行交互
    private var _client : URLProtocolClient?

    // 包装接口
    open var client: URLProtocolClient? {
        set { self._client = newValue }
        get { return self._client }
    }
    
    // 包装接口
    open var request: URLRequest {
        return _request
    }
    
    // 包装接口
    open var cachedResponse: CachedURLResponse? {
        return _cachedResponse
    }

    // 包装接口
    open var task: URLSessionTask? {
        set { self._task = newValue }
        get { return self._task }
    }

    
    open class func canInit(with request: URLRequest) -> Bool {
        NSRequiresConcreteImplementation()
    }
    
    open class func canonicalRequest(for request: URLRequest) -> URLRequest {
        NSRequiresConcreteImplementation()
    }
    
    open class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
        NSRequiresConcreteImplementation()
    }
    
    open func startLoading() {
        NSRequiresConcreteImplementation()
    }
    
    open func stopLoading() {
        NSRequiresConcreteImplementation()
    }
}

其中, 我们我们能理解, URLProtocol实例持有以下几个成员变量:

  1. _task: 每个 URLProtocol 都有一个 task
  2. _request: 是 task.originalRequest
  3. _client: 非常重要, 与task交互的胶水方法对象, 这里的类型是 _ProtocolClient, 但是真正有效的是URLProtocolClient 接口!!!
  4. _cachedResponse: 缓存对象, 这里先忽略

关于 URLProtocolClient, 它 提供了一套, URL Loading System 交互的 API, 简单来说 URLProtocol在脏活累活干完以后, 使用 _client.xxx() 方法与 Task 进行交互!!!

internal class _ProtocolClient : NSObject {
    var cachePolicy: URLCache.StoragePolicy = .notAllowed
    var cacheableData: [Data]?
    var cacheableResponse: URLResponse?
}

extension _ProtocolClient: URLProtocolClient {
    ...
}

public protocol URLProtocolClient : NSObjectProtocol {
    ...
}

核心的URLProtocol子类--_NativeProtocol

前面提到, URLProtocol._registeredProtocolClasses() 中拥有多个系统注册的URLProtocol.self类型, _NativeProtocol就是其中之一!!!

简单来说_NativeProtocol是一个包装层, 它是对_EasyHandle相关方法的包装, 真正的网络请求就是这里通过 curl 发起的!!!

_EasyHandle 是 swift 封装的 curl handle 句柄!!!

// 针对 HTTP/HTTPS/FTP Protocol 会使用 easyHandle 去实现
internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
    internal var easyHandle: _EasyHandle!
    
    // lazy声明 - 临时文件 -> 如果是普通文件下载, 需要支持 NSTemporaryDirectory()的实现
    internal lazy var tempFileURL: URL = {
        let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp"
        _ = FileManager.default.createFile(atPath: fileName, contents: nil)
        return URL(fileURLWithPath: fileName)
    }()

    // 默认实现方法!
    public required init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
        // _NativeProtocol 拥有一个内部状态机
        self.internalState = .initial
        super.init(request: task.originalRequest!, cachedResponse: cachedResponse, client: client)
        self.task = task
        // 初始化场景 - 主动创建 curl 的 _EasyHandle
        self.easyHandle = _EasyHandle(delegate: self)
    }

    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        return request
    }

    // 默认场景 - workQueue 中执行!!! 因此需要拿到 Client 需要执行的 queue
    override func startLoading() {
        resume()
    }

    // 通常来说是 对应 Task 的取消工作!!!
    override func stopLoading() {
        if task?.state == .suspended {
            suspend()
        } else {
            self.internalState = .transferFailed
            guard let error = self.task?.error else { fatalError() }
            completeTask(withError: error)
        }
    }

    ...
}

extension _NativeProtocol {
    // 内置的状态机 - 维护 curl 的请求核心的过程
    enum _InternalState {
        /// Task has been created, but nothing has been done, yet
        case initial
        /// The task is being fulfilled from the cache rather than the network. -> 如果拥有 cache
        case fulfillingFromCache(CachedURLResponse)
        /// The easy handle has been fully configured. But it is not added to the multi handle. -> 配置完成, 但是没有加入 multi-handle
        case transferReady(_TransferState)
        /// The easy handle is currently added to the multi handle -> 如果easyHandle 加入到 multi-handle, 真正的任务请求就会开始
        case transferInProgress(_TransferState)
        /// The transfer completed.
        ///
        /// The easy handle has been removed from the multi handle. This does
        /// not necessarily mean the task completed. A task that gets
        /// redirected will do multiple transfers.
        case transferCompleted(response: URLResponse, bodyDataDrain: _NativeProtocol._DataDrain)
        /// The transfer failed.
        ///
        /// Same as `.transferCompleted`, but without response / body data
        case transferFailed
        /// Waiting for the completion handler of the HTTP redirect callback.
        ///
        /// When we tell the delegate that we're about to perform an HTTP
        /// redirect, we need to wait for the delegate to let us know what
        /// action to take.
        case waitingForRedirectCompletionHandler(response: URLResponse, bodyDataDrain: _NativeProtocol._DataDrain)
        /// Waiting for the completion handler of the 'did receive response' callback.
        ///
        /// When we tell the delegate that we received a response (i.e. when
        /// we received a complete header), we need to wait for the delegate to
        /// let us know what action to take. In this state the easy handle is
        /// paused in order to suspend delegate callbacks.
        case waitingForResponseCompletionHandler(_TransferState)
        /// The task is completed
        ///
        /// Contrast this with `.transferCompleted`.
        case taskCompleted
    }

        // 内置 protocol 拥有 internal State
    var internalState: _InternalState {
        // We manage adding / removing the easy handle and pausing / unpausing
        // here at a centralized place to make sure the internal state always
        // matches up with the state of the easy handle being added and paused.
        willSet {
            if !internalState.isEasyHandlePaused && newValue.isEasyHandlePaused {
                fatalError("Need to solve pausing receive.")
            }
            
            // 维护 multiHandle 移除的操作
            if internalState.isEasyHandleAddedToMultiHandle && !newValue.isEasyHandleAddedToMultiHandle {
                task?.session.remove(handle: easyHandle)
            }
        }
        
        didSet {
            // multiHandle 持有
            if !oldValue.isEasyHandleAddedToMultiHandle && internalState.isEasyHandleAddedToMultiHandle {
                task?.session.add(handle: easyHandle)
            }
            
            if oldValue.isEasyHandlePaused && !internalState.isEasyHandlePaused {
                fatalError("Need to solve pausing receive.")
            }
        }
    }
}

其中, 通过_InternalState 能很清晰的看清整个网络请求的过程, 并且_NativeProtocol实例对象的状态是单向变化的!!!

并且, internalState注册了属性监听器, 在指定状态中, 会去与multi-handle进行交互.

curl 中 easyhandle 可以被添加到 multi-handle 中, multi-handle 来进行调度执行. 简单来说一个 multi-handle 能共用socket连接池, event-loop等...

具体 curl 如何使用 easyhandle, multi-handle 请参考其他资料

_NativeProtocol 启动任务的过程

internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {

    // 在 startLoading() 中执行, 并且是在 dataTask.workQueue 中调度的!!!
    func resume() {
        // 1. 当前
        if case .initial = self.internalState {
            guard let r = task?.originalRequest else {
                fatalError("Task has no original request.")
            }
            
            // 先去检测 cache!!!
            // Check if the cached response is good to use:
            if let cachedResponse = cachedResponse, canRespondFromCache(using: cachedResponse) {
                // cacheQueue 中异步回调
                self.internalState = .fulfillingFromCache(cachedResponse)
                
                // 我们自定义的API中, 拿不到 workQueue!!! 因此这里只能用比较hack的方法 调用
                // 但是这里本来就是在 workQueue 中执行, 又重新异步 workQueue.async ??
                task?.workQueue.async {
                    // 真实的服务 -> 无所谓调用
                    self.client?.urlProtocol(self, cachedResponseIsValid: cachedResponse)
                    // 直接调用 receive Responsd
                    self.client?.urlProtocol(self, didReceive: cachedResponse.response, cacheStoragePolicy: .notAllowed)
                    // 调用 didLoad:(data)
                    if !cachedResponse.data.isEmpty {
                        self.client?.urlProtocol(self, didLoad: cachedResponse.data)
                    }
                    
                    // 调用didFinishLoading -> 
                    self.client?.urlProtocolDidFinishLoading(self)
                    self.internalState = .taskCompleted
                }
            } else {
                // workQueue 中执行!
                startNewTransfer(with: r)
            }
        }

        if case .transferReady(let transferState) = self.internalState {
            self.internalState = .transferInProgress(transferState)
        }
    }

    /// Start a new transfer
    func startNewTransfer(with request: URLRequest) {
        let task = self.task!
        task.currentRequest = request 
        guard let url = request.url else {
            fatalError("No URL in request.")
        }

        // 同步方法!!!
        
        // 判断body 是否是 streamBody -> 如果是 streamBody
        task.getBody { (body) in
           // workQueue 中调用
            // 回调方法可能在其他线程!!! -> 内部状态-关联 transferState
            self.internalState = .transferReady(self.createTransferState(url: url, body: body, workQueue: task.workQueue))
            
            // 创建 一般都没有???
            let request = task.authRequest ?? request
            // 配置真实的 curl easy Handle ->
            self.configureEasyHandle(for: request, body: body)
            
            if (task.suspendCount) < 1 {
                // 真实的启动
                self.resume()
            }
        }
    }

    internal struct _TransferState {
        /// The URL that's being requested
        let url: URL
        /// Raw headers received.
        let parsedResponseHeader: _ParsedResponseHeader
        /// Once the headers is complete, this will contain the response
        var response: URLResponse?
        /// The body data to be sent in the request
        let requestBodySource: _BodySource? // 真实的 dataSource 最简单
        /// Body data received
        let bodyDataDrain: _DataDrain // 接受数据存储的地方: file/mem?
        /// Describes what to do with received body data for this transfer:
    }
}

从启动过程中主要分成两条线:

  1. 如果拥有cache:
    1. internalState = .fulfillingFromCache(cachedResponse)
    2. 调用ProtocolClient的核心过程
      1. urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse)
      2. urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
      3. urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
      4. urlProtocolDidFinishLoading(_ protocol: URLProtocol)
    3. internalState = .taskCompleted
  2. 没有cache, 直接调用startNewTransfer()启动任务:
    1. 调用task.getBody(<>), 获取 task之前抽象的_Body对象
    2. 切换internalState = .transferReady(self.createTransferState(url: url, body: body, workQueue: task.workQueue)), 核心是构造_TransferState
    3. 使用request + http body构造curl 的 easy-handle
    4. 重新调用 resume()方法, 此时由于interalState == .transferReady(...), 状态切换: internalState = .transferInProgress(transferState)
    5. 由于状态切换满足条件, 导致isEasyHandleAddedToMultiHandle == true!!!在internalStatewillSetdidSet中, 触发task?.session.add(handle: easyHandle), 会真正的启动请求!!!