El principio de la pila de protocolos de red de iOS (6) -- URLProtocolClient

El principio de la pila de protocolos de red de iOS (6) -- URLProtocolClient

URLProtocolClient- Datos del _NativeProtocolprotocolo que interactúa con las capas superiores

Como se puede ver en lo anterior, cuando comienza la devolución de llamada real de curl, algunos eventos deben _NativeProtocol/_HTTPURLProtocolvolver a llamarse a la capa superior. APPLE abstrae un protocolo para restringir el contenido del método específico:

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

Este conjunto de protocolos URLProtocolse usa mucho en varios métodos, como resumelos completeTaskmétodos y:

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{ ... }¡ Los métodos principales de lo anterior son básicamente los métodos para que el URLProtocolobjeto se URLSessionTaskcomunique con la capa superior!

URLProtocol/URLProtocolClient¡ El modo proxy utilizado aquí , podemos ver directamente las propiedades _NativeProtocolen el objeto de esta manera !Se puede entender bien.open var client: URLProtocolClient?var delegate: URLProtocolDelegate?

Además, desde una perspectiva global:

  1. URLSessionTaskSe entrega el proceso de solicitud de datos básicosURLProtocol
  2. URLProtocolPara aislar los datos de la devolución de llamada, se usa el modo proxy, y el objeto proxy es el mismo URLProtocol En el constructor, se crea internamente un objeto de instancia private var _client : URLProtocolClient?, que en realidad es class _ProtocolClient: URLProtocolClientuna instancia de .
  3. _ProtocolClientEnvuelve una gran cantidad de métodos de devolución de llamada, completamente URLProtocoldespojados del alcance de:
    1. Devolución de llamada cuando URLProtocol recibe encabezado de respuesta HTTP --urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
    2. Devolución de llamada de URLProtocol cuando se reciben datos de respuesta HTTP (se puede llamar varias veces) --urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
    3. Devolución de llamada cuando URLProtocol finaliza la transferencia de datos --urlProtocolDidFinishLoading(_ protocol: URLProtocol)
    4. Devolución de llamada de URLProtocol cuando hay un error en la transferencia de datos --urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)
    5. ...

_ProtocolClient- Una clase contenedora que realmente ayuda _NativeProtocolcon las devoluciones de llamadas .

Echemos un breve vistazo a su definición y luego implementemos la URLProtocolClientextensión:

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

Supongo que te gusta

Origin juejin.im/post/7120082480031334408
Recomendado
Clasificación