iOS网络协议栈原理(六) — URLProtocolClient

URLProtocolClient – 数据从_NativeProtocol 与上层交互的协议

从前面看出, 在curl真实的回调出发时, 部分事件是需求_NativeProtocol/_HTTPURLProtocol向上层回调的, APPLE 抽象了一个协议来约束详细的办法内容:

/*!
@protocol URLProtocolClient
@discussion URLProtocolClient provides the interface to the URL
loading system that is intended for use by URLProtocol
implementors.
*/
public protocol URLProtocolClient : NSObjectProtocol {
    /*!
     @method URLProtocol:wasRedirectedToRequest:
     @abstract Indicates to an URLProtocolClient that a redirect has
     occurred.
     @param URLProtocol the URLProtocol object sending the message.
     @param request the NSURLRequest to which the protocol implementation
     has redirected.
     */
    func urlProtocol(_ protocol: URLProtocol, wasRedirectedTo request: URLRequest, redirectResponse: URLResponse)
    /*!
     @method URLProtocol:cachedResponseIsValid:
     @abstract Indicates to an URLProtocolClient that the protocol
     implementation has examined a cached response and has
     determined that it is valid.
     @param URLProtocol the URLProtocol object sending the message.
     @param cachedResponse the NSCachedURLResponse object that has
     examined and is valid.
     */
    func urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse)
    /*!
     @method URLProtocol:didReceiveResponse:
     @abstract Indicates to an URLProtocolClient that the protocol
     implementation has created an URLResponse for the current load.
     @param URLProtocol the URLProtocol object sending the message.
     @param response the URLResponse object the protocol implementation
     has created.
     @param cacheStoragePolicy The URLCache.StoragePolicy the protocol
     has determined should be used for the given response if the
     response is to be stored in a cache.
     */
    func urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
    /*!
     @method URLProtocol:didLoadData:
     @abstract Indicates to an NSURLProtocolClient that the protocol
     implementation has loaded URL data.
     @discussion The data object must contain only new data loaded since
     the previous call to this method (if any), not cumulative data for
     the entire load.
     @param URLProtocol the NSURLProtocol object sending the message.
     @param data URL load data being made available.
     */
    func urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
    /*!
     @method URLProtocolDidFinishLoading:
     @abstract Indicates to an NSURLProtocolClient that the protocol
     implementation has finished loading successfully.
     @param URLProtocol the NSURLProtocol object sending the message.
     */
    func urlProtocolDidFinishLoading(_ protocol: URLProtocol)
    /*!
     @method URLProtocol:didFailWithError:
     @abstract Indicates to an NSURLProtocolClient that the protocol
     implementation has failed to load successfully.
     @param URLProtocol the NSURLProtocol object sending the message.
     @param error The error that caused the load to fail.
     */
    func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)
    /*!
     @method URLProtocol:didReceiveAuthenticationChallenge:
     @abstract Start authentication for the specified request
     @param protocol The protocol object requesting authentication.
     @param challenge The authentication challenge.
     @discussion The protocol client guarantees that it will answer the
     request on the same thread that called this method. It may add a
     default credential to the challenge it issues to the connection delegate,
     if the protocol did not provide one.
     */
    func urlProtocol(_ protocol: URLProtocol, didReceive challenge: URLAuthenticationChallenge)
    /*!
     @method URLProtocol:didCancelAuthenticationChallenge:
     @abstract Cancel authentication for the specified request
     @param protocol The protocol object cancelling authentication.
     @param challenge The authentication challenge.
     */
    func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge)
}

这套协议, 在URLProtocol的几个办法顶用的非常多, 例如 resumecompleteTask 办法:

internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
    ...
    func resume() {
        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)
        }
    }
    func completeTask() {
        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = self.internalState else {
            fatalError("Trying to complete the task, but its transfer isn't complete.")
        }
        task?.response = response
        // We don't want a timeout to be triggered after this. The timeout timer needs to be cancelled.
        easyHandle.timeoutTimer = nil
        // because we deregister the task with the session on internalState being set to taskCompleted
        // we need to do the latter after the delegate/handler was notified/invoked
        if case .inMemory(let bodyData) = bodyDataDrain {
            var data = Data()
            if let body = bodyData {
                withExtendedLifetime(body) {
                    data = Data(bytes: body.bytes, count: body.length)
                }
            }
            self.client?.urlProtocol(self, didLoad: data)
            self.internalState = .taskCompleted
        } else if case .toFile(let url, let fileHandle?) = bodyDataDrain {
            self.properties[.temporaryFileURL] = url
            fileHandle.closeFile()
        } else if task is URLSessionDownloadTask {
            let fileHandle = try! FileHandle(forWritingTo: self.tempFileURL)
            fileHandle.closeFile()
            self.properties[.temporaryFileURL] = self.tempFileURL
        }
        // 调用 loadData -> 直接调用 FinishLoading
        self.client?.urlProtocolDidFinishLoading(self)
        self.internalState = .taskCompleted
    }
}

从以上的 Protocol ProtocolClient{ ... } 的中心办法基本都是 URLProtocol 目标与上层URLSessionTask通讯的办法!!!

这儿URLProtocol/URLProtocolClient使用的署理形式, 咱们能够将_NativeProtocol目标中的open var client: URLProtocolClient?属性, 直接看做这样var delegate: URLProtocolDelegate?!!! 就能很好理解了.

别的, 从大局来看:

  1. URLSessionTask的中心数据请求的进程交给了URLProtocol
  2. URLProtocol为了阻隔向上回调的数据, 使用了署理形式, 并且署理目标是URLProtocol自己 !!! 在结构函数时, 内部创立一个private var _client : URLProtocolClient?, 实践是class _ProtocolClient: URLProtocolClient 的实例目标.
  3. _ProtocolClient包装了许多向上回调的办法, 彻底从URLProtocol的作用域中剥离出来:

    1. URLProtocol收到HTTP Response Header时 回调 — urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
    2. URLProtocol 收到 HTTP Response Data时 回调(可能多次回调) — urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
    3. URLProtocol 结束数据传输时回调 — urlProtocolDidFinishLoading(_ protocol: URLProtocol)
    4. URLProtocol 在数据传输中出错时回调 — urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)

_ProtocolClient – 真正协助_NativeProtocol进行向上回调的包装类

先简略看一下, 它的界说, 然后实现了URLProtocolClient扩展:

/*
 多个维度的缓存:
 1. 缓存策略 cachePolicy
 2. cacheableData 二进制
 3. response 缓存
 实现 ProtocolClient Protocol
 */
internal class _ProtocolClient : NSObject {
    var cachePolicy: URLCache.StoragePolicy = .notAllowed
    var cacheableData: [Data]?
    var cacheableResponse: URLResponse?
}
/// 详细的代码能够参阅 swift-foundation 源码
extension _ProtocolClient: URLProtocolClient {
    ...
}

源代码中能够整理出, _ProtocolClient 在实现URLProtocolClient 进程中, 还协助完结了如下的工作:

  1. response cache: HTTPResponse Cache相关的内容, 包含 response header 和 response data!
  2. authenticationChallenge: HTTPS 证书鉴权的工作, 以及对Session几遍的HTTPS鉴权结果进行缓存, 等候后续复用.
  3. 依据Task的回调方法(delegate, block), 调用task.delegate不同的回调办法!!!
  4. 在URLProtocol告知Client请求结束时, 进行session.taskRegistry.remove(task)操作!!!

两句话小结URLProtocolURLProtocolClient的联系:

  1. URLProtocol的本职工作是获取数据, 它只关怀获取数据的进程与结果, 详细将结果交给谁, 它并不关怀!!!
  2. 引进URLProtocolClient来解耦URLProtocol, 将URLProtocol的非本职工作一致提取到URLProtocolClient. 本质上是托付的规划形式!!!