iOS网络协议栈原理(四) -- 真正的干活类_NativeProtocol的启动请求的过程
URLProtocl的接口
前面提到真正请求是在task.resume()时, 会经历如下步骤:
- 构造一个
[URLProtocol.self]()数组!!! 来源是Session.config.protocolClasses和URLProtocol._registeredProtocolClasses和组合!! - 从这个
[URLProtocol.self]()中进行遍历,选择一个满足条件条件的URLProtocl类型,- 条件:
class func canInit(with task: URLSessionTask) -> Bool;也就是判断哪个URLProtocl类型能处理这个task
- 条件:
- 选中的
URLProtocl, 调用构造函数public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?), 创建实例 - 调用
urlprotocl.startLoading()方法真正的启动 task 任务!!!
下面是URLProtocol的核心接口, 可以参考代码注释:
open class URLProtocol : NSObject {
...
// 默认 `_HTTPURLProtocol` `_FTPURLProtocol` `_DataURLProtocol`
private static var _registeredProtocolClasses = [AnyClass]()
// 核心方法 -> task 底层也会去调用 canInit(with request:) 的成员方法
// 条件: 以上第二步会调用的方法
open class func canInit(with task: URLSessionTask) -> Bool {
guard let request = task.currentRequest else { return false }
return canInit(with: request)
}
// 以上第三步创建的 URLProtocol 实例!!!
public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
let urlRequest = task.originalRequest
self.init(request: urlRequest!, cachedResponse: cachedResponse, client: client)
self.task = task // URLProtocol 内部会持有上层的 task
}
public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
self._request = request
self._cachedResponse = cachedResponse
self._client = client ?? _ProtocolClient()
}
/// 核心成员变量
private var _task : URLSessionTask? = nil
// 持有task.originalRequest
private var _request : URLRequest
// 核心成员!!! => 将来会与 task进行交互
private var _client : URLProtocolClient?
// 包装接口
open var client: URLProtocolClient? {
set { self._client = newValue }
get { return self._client }
}
// 包装接口
open var request: URLRequest {
return _request
}
// 包装接口
open var cachedResponse: CachedURLResponse? {
return _cachedResponse
}
// 包装接口
open var task: URLSessionTask? {
set { self._task = newValue }
get { return self._task }
}
open class func canInit(with request: URLRequest) -> Bool {
NSRequiresConcreteImplementation()
}
open class func canonicalRequest(for request: URLRequest) -> URLRequest {
NSRequiresConcreteImplementation()
}
open class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
NSRequiresConcreteImplementation()
}
open func startLoading() {
NSRequiresConcreteImplementation()
}
open func stopLoading() {
NSRequiresConcreteImplementation()
}
}
其中, 我们我们能理解, URLProtocol实例持有以下几个成员变量:
_task: 每个 URLProtocol 都有一个 task_request: 是task.originalRequest_client: 非常重要, 与task交互的胶水方法对象, 这里的类型是_ProtocolClient, 但是真正有效的是URLProtocolClient接口!!!_cachedResponse: 缓存对象, 这里先忽略
关于 URLProtocolClient, 它 提供了一套, URL Loading System 交互的 API, 简单来说 URLProtocol在脏活累活干完以后, 使用 _client.xxx() 方法与 Task 进行交互!!!
internal class _ProtocolClient : NSObject {
var cachePolicy: URLCache.StoragePolicy = .notAllowed
var cacheableData: [Data]?
var cacheableResponse: URLResponse?
}
extension _ProtocolClient: URLProtocolClient {
...
}
public protocol URLProtocolClient : NSObjectProtocol {
...
}
核心的URLProtocol子类--_NativeProtocol
前面提到, URLProtocol._registeredProtocolClasses() 中拥有多个系统注册的URLProtocol.self类型, _NativeProtocol就是其中之一!!!
简单来说_NativeProtocol是一个包装层, 它是对_EasyHandle相关方法的包装, 真正的网络请求就是这里通过 curl 发起的!!!
_EasyHandle 是 swift 封装的 curl handle 句柄!!!
// 针对 HTTP/HTTPS/FTP Protocol 会使用 easyHandle 去实现
internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
internal var easyHandle: _EasyHandle!
// lazy声明 - 临时文件 -> 如果是普通文件下载, 需要支持 NSTemporaryDirectory()的实现
internal lazy var tempFileURL: URL = {
let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp"
_ = FileManager.default.createFile(atPath: fileName, contents: nil)
return URL(fileURLWithPath: fileName)
}()
// 默认实现方法!
public required init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
// _NativeProtocol 拥有一个内部状态机
self.internalState = .initial
super.init(request: task.originalRequest!, cachedResponse: cachedResponse, client: client)
self.task = task
// 初始化场景 - 主动创建 curl 的 _EasyHandle
self.easyHandle = _EasyHandle(delegate: self)
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
// 默认场景 - workQueue 中执行!!! 因此需要拿到 Client 需要执行的 queue
override func startLoading() {
resume()
}
// 通常来说是 对应 Task 的取消工作!!!
override func stopLoading() {
if task?.state == .suspended {
suspend()
} else {
self.internalState = .transferFailed
guard let error = self.task?.error else { fatalError() }
completeTask(withError: error)
}
}
...
}
extension _NativeProtocol {
// 内置的状态机 - 维护 curl 的请求核心的过程
enum _InternalState {
/// Task has been created, but nothing has been done, yet
case initial
/// The task is being fulfilled from the cache rather than the network. -> 如果拥有 cache
case fulfillingFromCache(CachedURLResponse)
/// The easy handle has been fully configured. But it is not added to the multi handle. -> 配置完成, 但是没有加入 multi-handle
case transferReady(_TransferState)
/// The easy handle is currently added to the multi handle -> 如果easyHandle 加入到 multi-handle, 真正的任务请求就会开始
case transferInProgress(_TransferState)
/// The transfer completed.
///
/// The easy handle has been removed from the multi handle. This does
/// not necessarily mean the task completed. A task that gets
/// redirected will do multiple transfers.
case transferCompleted(response: URLResponse, bodyDataDrain: _NativeProtocol._DataDrain)
/// The transfer failed.
///
/// Same as `.transferCompleted`, but without response / body data
case transferFailed
/// Waiting for the completion handler of the HTTP redirect callback.
///
/// When we tell the delegate that we're about to perform an HTTP
/// redirect, we need to wait for the delegate to let us know what
/// action to take.
case waitingForRedirectCompletionHandler(response: URLResponse, bodyDataDrain: _NativeProtocol._DataDrain)
/// Waiting for the completion handler of the 'did receive response' callback.
///
/// When we tell the delegate that we received a response (i.e. when
/// we received a complete header), we need to wait for the delegate to
/// let us know what action to take. In this state the easy handle is
/// paused in order to suspend delegate callbacks.
case waitingForResponseCompletionHandler(_TransferState)
/// The task is completed
///
/// Contrast this with `.transferCompleted`.
case taskCompleted
}
// 内置 protocol 拥有 internal State
var internalState: _InternalState {
// We manage adding / removing the easy handle and pausing / unpausing
// here at a centralized place to make sure the internal state always
// matches up with the state of the easy handle being added and paused.
willSet {
if !internalState.isEasyHandlePaused && newValue.isEasyHandlePaused {
fatalError("Need to solve pausing receive.")
}
// 维护 multiHandle 移除的操作
if internalState.isEasyHandleAddedToMultiHandle && !newValue.isEasyHandleAddedToMultiHandle {
task?.session.remove(handle: easyHandle)
}
}
didSet {
// multiHandle 持有
if !oldValue.isEasyHandleAddedToMultiHandle && internalState.isEasyHandleAddedToMultiHandle {
task?.session.add(handle: easyHandle)
}
if oldValue.isEasyHandlePaused && !internalState.isEasyHandlePaused {
fatalError("Need to solve pausing receive.")
}
}
}
}
其中, 通过_InternalState 能很清晰的看清整个网络请求的过程, 并且_NativeProtocol实例对象的状态是单向变化的!!!
并且, internalState注册了属性监听器, 在指定状态中, 会去与multi-handle进行交互.
curl 中 easyhandle 可以被添加到 multi-handle 中, multi-handle 来进行调度执行. 简单来说一个 multi-handle 能共用socket连接池, event-loop等...
具体 curl 如何使用 easyhandle, multi-handle 请参考其他资料
_NativeProtocol 启动任务的过程
internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
// 在 startLoading() 中执行, 并且是在 dataTask.workQueue 中调度的!!!
func resume() {
// 1. 当前
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)
}
}
/// Start a new transfer
func startNewTransfer(with request: URLRequest) {
let task = self.task!
task.currentRequest = request
guard let url = request.url else {
fatalError("No URL in request.")
}
// 同步方法!!!
// 判断body 是否是 streamBody -> 如果是 streamBody
task.getBody { (body) in
// workQueue 中调用
// 回调方法可能在其他线程!!! -> 内部状态-关联 transferState
self.internalState = .transferReady(self.createTransferState(url: url, body: body, workQueue: task.workQueue))
// 创建 一般都没有???
let request = task.authRequest ?? request
// 配置真实的 curl easy Handle ->
self.configureEasyHandle(for: request, body: body)
if (task.suspendCount) < 1 {
// 真实的启动
self.resume()
}
}
}
internal struct _TransferState {
/// The URL that's being requested
let url: URL
/// Raw headers received.
let parsedResponseHeader: _ParsedResponseHeader
/// Once the headers is complete, this will contain the response
var response: URLResponse?
/// The body data to be sent in the request
let requestBodySource: _BodySource? // 真实的 dataSource 最简单
/// Body data received
let bodyDataDrain: _DataDrain // 接受数据存储的地方: file/mem?
/// Describes what to do with received body data for this transfer:
}
}
从启动过程中主要分成两条线:
- 如果拥有cache:
internalState = .fulfillingFromCache(cachedResponse)- 调用
ProtocolClient的核心过程urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse)urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy)urlProtocol(_ protocol: URLProtocol, didLoad data: Data)urlProtocolDidFinishLoading(_ protocol: URLProtocol)
internalState = .taskCompleted
- 没有cache, 直接调用
startNewTransfer()启动任务:- 调用
task.getBody(<>), 获取 task之前抽象的_Body对象 - 切换
internalState = .transferReady(self.createTransferState(url: url, body: body, workQueue: task.workQueue)), 核心是构造_TransferState - 使用
request+http body构造curl 的 easy-handle - 重新调用
resume()方法, 此时由于interalState == .transferReady(...), 状态切换:internalState = .transferInProgress(transferState) - 由于状态切换满足条件, 导致
isEasyHandleAddedToMultiHandle == true!!!在internalState的willSet和didSet中, 触发task?.session.add(handle: easyHandle), 会真正的启动请求!!!
- 调用