iOS网络协议栈原理(二) — URLSessionTask层

上一篇文章总结到, URLSession 是一个出产URLSessionDataTask的工厂, 其中有几个要害内二进制的运算规则容, 会在这一节中解释:

  1. 在创立task时, 会初始化失败是怎么解决URLSession实例传入, 也二进制便是说 task 可能会在内部持有 URLSession
  2. 真实建议恳求时, 需求调用task.resume()办法 — 那么Ta状态机是什么sk究竟是如何作业的

1. Task的初始化创立与HTTP B容器是什么ody包装

简略概括一下Task的初始化的要害信息:

  1. Task的回调办法: 相关回调办法, 是经过Behavappearanceiour + taskIdentifier状态机的概念储在URLSession
  2. _Body枚举: 根据URLReqapproachuesthttpBody or httpBodyStream的成员, 在内部一致运用_Bo状态机模式dy枚举收口, 具会分成4类.
  3. 参阅以下代码注释
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??初始化sdk什么意思?

实际上在task内部维护了一个state!!! 而且经过一个成员变量s容器英文uspend容器的容积一定比它的体积小Coun初始化磁盘t来记录这个Tappearask使命是否在挂起appstore状态!!!

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中完结的:

  1. 初始化时, task.state => .suspe初始化失败是怎么解决nded
  2. 当外部调用resume(), task.state => .running, 然后真实发动耗时使命, 详细的耗时使命是交给别的一个URLP容器是什么rotocl状态机编程的实例完结的, 详细发动使命的逻辑我们后边分析!!!
  3. 当外部调用cance(), task.st容器苗ate => .canceling, 表明task现已撤销, 然后去处理底层的资源清理作业
  4. task相关的URLProtocl的实例的清理作业完结今后, 会调用task.state => .completed!!!

因此, 简略来说, Task并不是自己去履行网络恳求approve, 而是又笼统了一层, 真实的苦力活都是交给URLProtoco状态机l去做的!!!

别的Task功能性不同, Apple 创立状态机编程多个子类来进行差异化使命的实现, 别的, task progress 以及HTTP 302重定向等逻辑都是在task层实现的:

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 {
    ...
}