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
的中心数据请求的进程交给了URLProtocol
-
URLProtocol
为了阻隔向上回调的数据, 使用了署理形式, 并且署理目标是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
. 本质上是托付的规划形式!!!