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的几个方法中用的非常多, 例如 resume 和 completeTask 方法:
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?!!! 就能很好理解了.
另外, 从全局来看:
URLSessionTask的核心数据请求的过程交给了URLProtocolURLProtocol为了隔离向上回调的数据, 使用了代理模式, 并且代理对象是URLProtocol自己 !!! 在构造函数时, 内部创建一个private var _client : URLProtocolClient?, 实际是class _ProtocolClient: URLProtocolClient的实例对象._ProtocolClient包装了很多向上回调的方法, 完全从URLProtocol的作用域中剥离出来:- URLProtocol收到HTTP Response Header时 回调 --
urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy) - URLProtocol 收到 HTTP Response Data时 回调(可能多次回调) --
urlProtocol(_ protocol: URLProtocol, didLoad data: Data) - URLProtocol 结束数据传输时回调 --
urlProtocolDidFinishLoading(_ protocol: URLProtocol) - URLProtocol 在数据传输中出错时回调 --
urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error) - ...
- URLProtocol收到HTTP Response Header时 回调 --
_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 过程中, 还帮助完成了如下的事情:
- 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. 本质上是委托的设计模式!!!