The principle of iOS network protocol stack (6) -- URLProtocolClient

The principle of iOS network protocol stack (6) -- URLProtocolClient

URLProtocolClient- Data from _NativeProtocolthe protocol that interacts with the upper layer

As can be seen from the above, when curl's real callback starts, some events need _NativeProtocol/_HTTPURLProtocolto be called back to the upper layer. APPLE abstracts a protocol to constrain the specific method content:

/*!
@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)
}
复制代码

This set of protocols URLProtocolis used a lot in several methods, such as resumeand completeTaskmethods:

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{ ... }The core methods from the above are basically the methods for the URLProtocolobject to URLSessionTaskcommunicate with the upper layer!!!

URLProtocol/URLProtocolClientThe proxy mode used here , we can directly see the properties _NativeProtocolin the object like this !!! It can be well understood.open var client: URLProtocolClient?var delegate: URLProtocolDelegate?

Also, from a global perspective:

  1. URLSessionTaskThe core data request process is handed overURLProtocol
  2. URLProtocolIn order to isolate the data of the callback, the proxy mode is used, and the proxy object is URLProtocol itself !!! In the constructor, an instance object is created internally private var _client : URLProtocolClient?, which is actually class _ProtocolClient: URLProtocolClientan instance of .
  3. _ProtocolClientWraps a lot of callback methods, completely URLProtocolstripped from the scope of:
    1. Callback when URLProtocol receives HTTP Response Header --urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
    2. URLProtocol Callback when HTTP Response Data is received (may be called multiple times) --urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
    3. Callback when URLProtocol ends data transfer --urlProtocolDidFinishLoading(_ protocol: URLProtocol)
    4. URLProtocol callback when there is an error in data transfer --urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)
    5. ...

_ProtocolClient- A wrapper class that really helps _NativeProtocolwith callbacks

Let's take a brief look at its definition, and then implement the URLProtocolClientextension:

/*
 多个维度的缓存:
 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. 本质上是委托的设计模式!!!

Guess you like

Origin juejin.im/post/7120082480031334408