The principle of iOS network protocol stack (6) -- URLProtocolClient
URLProtocolClient
- Data from _NativeProtocol
the protocol that interacts with the upper layer
As can be seen from the above, when curl's real callback starts, some events need _NativeProtocol/_HTTPURLProtocol
to 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 URLProtocol
is used a lot in several methods, such as resume
and completeTask
methods:
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 URLProtocol
object to URLSessionTask
communicate with the upper layer!!!
URLProtocol/URLProtocolClient
The proxy mode used here , we can directly see the properties_NativeProtocol
in the object like this !!! It can be well understood.open var client: URLProtocolClient?
var delegate: URLProtocolDelegate?
Also, from a global perspective:
URLSessionTask
The core data request process is handed overURLProtocol
URLProtocol
In 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 internallyprivate var _client : URLProtocolClient?
, which is actuallyclass _ProtocolClient: URLProtocolClient
an instance of ._ProtocolClient
Wraps a lot of callback methods, completelyURLProtocol
stripped from the scope of:- Callback when URLProtocol receives HTTP Response Header --
urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)
- URLProtocol Callback when HTTP Response Data is received (may be called multiple times) --
urlProtocol(_ protocol: URLProtocol, didLoad data: Data)
- Callback when URLProtocol ends data transfer --
urlProtocolDidFinishLoading(_ protocol: URLProtocol)
- URLProtocol callback when there is an error in data transfer --
urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)
- ...
- Callback when URLProtocol receives HTTP Response Header --
_ProtocolClient
- A wrapper class that really helps _NativeProtocol
with callbacks
Let's take a brief look at its definition, and then implement the URLProtocolClient
extension:
/*
多个维度的缓存:
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
. 本质上是委托的设计模式!!!