iOS網路協議棧原理(六) -- URLProtocolClient
iOS網路協議棧原理(六) -- URLProtocolClient
URLProtocolClient
- 資料從_NativeProtocol
與上層互動的協議
從前面看出, 在curl真實的回調出發時, 部分事件是需要_NativeProtocol/_HTTPURLProtocol
向上層回撥的, APPLE 抽象了一個協議來約束具體的方法內容:
```swift /! @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
方法:
```swift 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
擴充套件:
```swift /* 多個維度的快取: 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
. 本質上是委託的設計模式!!!
- iOS RN啟動中 Native Module 是如何被管理的
- iOS網路協議棧原理(六) -- URLProtocolClient
- Flutter中Widget的生命週期
- iOS網路協議棧原理(二) -- URLSessionTask層
- iOS網路協議棧原理(一) -- URLSession簡介
- OC歸納總結 -- (2)OC物件@property的修飾符
- iOS APP中pre-main都做了什麼
- OC歸納總結 -- (3)OC物件的記憶體管理
- 重學AutoLayout (2) -- UIStackView
- iOS 中異常產生與捕獲小結
- Matrix-iOS之卡頓監控梳理
- 如何獲取iOS的執行緒呼叫棧
- ARM64彙編入門小記
- 再探iOS中的野指標問題
- 實戰中的isa swizzling的小結
- OC歸納總結 -- (8)OC底層之KVO