El principio de la pila de protocolos de red de iOS (6) -- URLProtocolClient
URLProtocolClient
- Datos del _NativeProtocol
protocolo 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/_HTTPURLProtocol
volver 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 URLProtocol
se usa mucho en varios métodos, como resume
los completeTask
mé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 URLProtocol
objeto se URLSessionTask
comunique con la capa superior!
URLProtocol/URLProtocolClient
¡ El modo proxy utilizado aquí , podemos ver directamente las propiedades_NativeProtocol
en el objeto de esta manera !Se puede entender bien.open var client: URLProtocolClient?
var delegate: URLProtocolDelegate?
Además, desde una perspectiva global:
URLSessionTask
Se entrega el proceso de solicitud de datos básicosURLProtocol
URLProtocol
Para 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 instanciaprivate var _client : URLProtocolClient?
, que en realidad esclass _ProtocolClient: URLProtocolClient
una instancia de ._ProtocolClient
Envuelve una gran cantidad de métodos de devolución de llamada, completamenteURLProtocol
despojados del alcance de:- Devolución de llamada cuando URLProtocol recibe encabezado de respuesta HTTP --
urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
- Devolución de llamada de URLProtocol cuando se reciben datos de respuesta HTTP (se puede llamar varias veces) --
urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
- Devolución de llamada cuando URLProtocol finaliza la transferencia de datos --
urlProtocolDidFinishLoading(_ protocol: URLProtocol)
- Devolución de llamada de URLProtocol cuando hay un error en la transferencia de datos --
urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)
- ...
- Devolución de llamada cuando URLProtocol recibe encabezado de respuesta HTTP --
_ProtocolClient
- Una clase contenedora que realmente ayuda _NativeProtocol
con las devoluciones de llamadas .
Echemos un breve vistazo a su definición y luego implementemos la URLProtocolClient
extensió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
过程中, 还帮助完成了如下的事情:
- response cache: HTTPResponse Cache相关的内容, 包括 response header 和 response data!
- authenticationChallenge: HTTPS 证书鉴权的工作, 以及对Session几遍的HTTPS鉴权结果进行缓存, 等待后续复用.
- 根据Task的回调方式(delegate, block), 调用
task.delegate
不同的回调方法!!! - 在URLProtocol告知Client请求结束时, 进行
session.taskRegistry.remove(task)
操作!!!
两句话小结URLProtocol
与URLProtocolClient
的关系:
URLProtocol
的本职工作是获取数据
, 它只关心获取数据的过程与结果, 具体将结果交给谁, 它并不关心!!!- 引入
URLProtocolClient
来解耦URLProtocol
, 将URLProtocol
的非本职工作统一提取到URLProtocolClient
. 本质上是委托的设计模式!!!