iOS網路協議棧原理(二) -- URLSessionTask層
iOS網路協議棧原理(二) -- URLSessionTask層
上一篇文章總結到, URLSession
是一個生產URLSessionDataTask
的工廠, 其中有幾個關鍵內容, 會在這一節中解釋:
- 在建立
task
時, 會將URLSession
例項傳入, 也就是說task
可能會在內部持有URLSession
- 真正發起請求時, 需要呼叫
task.resume()
方法 -- 那麼Task
究竟是如何工作的
1. Task
的初始化建立與HTTP Body
包裝
簡單歸納一下Task的初始化的關鍵資訊:
- Task的回撥方式: 關聯回撥方式, 是通過
Behaviour + taskIdentifier
儲存在URLSession
中 _Body列舉
: 根據URLRequest
的httpBody or httpBodyStream
的成員, 在內部統一使用_Body列舉
收口, 具會分成4類.- 參考以下程式碼註釋
```swift open class URLSessionTask : NSObject, NSCopying { ...
// 構造 SessionTask 的 Body 可能有4種情況
enum _Body {
case none
case data(DispatchData) // 為啥用 DispatchData 是一個內部二進位制容器型別, 實際會防止copy, 和傳入的 request.httpBody 共用同一份記憶體
/// Body data is read from the given file URL
case file(URL) // 其他的 Task會使用 URL模式的 body
case stream(InputStream)
}
// 核心方法!
internal convenience init(session: URLSession, request: URLRequest, taskIdentifier: Int) {
if let bodyData = request.httpBody, !bodyData.isEmpty {
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.data(createDispatchData(bodyData)))
} else if let bodyStream = request.httpBodyStream {
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.stream(bodyStream))
} else {
// 其他場景, 沒有body -> 例如使用 GET 請求時
self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: _Body.none)
}
}
// 更加重要的方法
internal init(session: URLSession, request: URLRequest, taskIdentifier: Int, body: _Body?) {
// 1. task 會強引用建立它的 session, 並且在 task 結束的時候 self.session = nil
self.session = session
// 2. 將 workQueue的 targetQueue 設定成 session.workQueue
// 3. 後續有大量關於 task.state 的操作都在這個 serial queue 中完成!!!
self.workQueue = DispatchQueue.init(label: "org.swift.URLSessionTask.WorkQueue", target: session.workQueue)
// 4. 唯一 task Identifier, 使用這個唯一標記, 可以通過 URLSession 關聯 CompletionHandler
self.taskIdentifier = taskIdentifier
// 5. 持有一份外部傳入的 Request!!! 因為整個請求過程中, 可能遇到 HTTP Status Code = 302的情況, 需要重定向, 可能一次Task過程會發起多個請求
self.originalRequest = request // 原始的請求
// 6. 收口以後的 http body
self.knownBody = body
super.init()
// 7. 同 5, 初始化時, 當前的請求就是初始化的request (後面如果有302重定向場景, Task會建立一個新的指向重定向路徑的request, 那時會修改 currentRequest)
self.currentRequest = request
// 8. 忽略...
self.progress.cancellationHandler = { [weak self] in
self?.cancel()
}
}
} ```
2. Task
的內部狀態機
前面我們知道, 在使用Session
工廠, 建立Task
例項以後, 需要主動呼叫task.resume()
方法啟動這個任務
!!! why???
實際上在task內部維護了一個state
!!! 並且通過一個成員變數suspendCount
來記錄這個Task任務
是否在掛起狀態!!!
```swift extension URLSessionTask {
/// How many times the task has been suspended, 0 indicating a running task.
internal var suspendCount = 1
public enum State : Int {
/// The task is currently being serviced by the session
case running
case suspended // 初始是 suspended
/// The task has been told to cancel. The session will receive a URLSession:task:didCompleteWithError: message.
case canceling // 退出 ing!!!!!! 只是標記 canceling -> 但是沒 completed
/// The task has completed and the session will receive no more delegate notifications
case completed
}
/// Updates the (public) state based on private / internal state.
///
/// - Note: This must be called on the `workQueue`.
internal func updateTaskState() {
func calculateState() -> URLSessionTask.State {
if suspendCount == 0 {
return .running
} else {
return .suspended
}
}
state = calculateState()
}
// 核心方法
open func resume() {
workQueue.sync {
// 忽略 canceling 和 completed
guard self.state != .canceling, self.state != .completed else {
return
}
if self.suspendCount > 0 {
self.suspendCount -= 1
}
self.updateTaskState()
if self.suspendCount == 0 {
...
// 真正的啟動任務
}
}
}
// 核心方法
open func cancel() {
workQueue.sync {
let canceled = self.syncQ.sync { () -> Bool in
guard self._state == .running || self._state == .suspended else {
return true
}
self._state = .canceling
return false
}
guard !canceled else {
return
}
...
}
}
} ```
通過以上程式碼能看出task
的兩個核心方法, 主要就是切換task.state
, 並且關於state
的操作都是在workQueue
中完成的:
- 初始化時,
task.state => .suspended
- 當外部呼叫
resume()
,task.state => .running
, 然後真正啟動耗時任務, 具體的耗時任務是交給另外一個URLProtocl的例項
完成的, 具體啟動任務的邏輯我們後面分析!!! - 當外部呼叫
cance()
,task.state => .canceling
, 表示task
已經取消, 然後去處理底層的資源清理工作 - 在
task
關聯的URLProtocl的例項
的清理工作完成以後, 會呼叫task.state => .completed
!!!
因此, 簡單來說, Task
並不是自己去執行網路請求, 而是又抽象了一層, 真正的苦力活都是交給URLProtocol
去做的!!!
另外Task
功能性不同, Apple 建立多個子類來進行差異化任務的實現, 另外, task progress
以及HTTP 302
重定向等邏輯都是在task層
實現的:
```swift open class URLSessionDataTask: URLSessionTask {}
/ * An URLSessionUploadTask does not currently provide any additional * functionality over an URLSessionDataTask. All delegate messages * that may be sent referencing an URLSessionDataTask equally apply * to URLSessionUploadTasks. / open class URLSessionUploadTask: URLSessionDataTask {}
/ * URLSessionDownloadTask is a task that represents a download to * local storage. / open class URLSessionDownloadTask: URLSessionTask { ... } ```
- 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