iOS网络协议栈原理(一) — URLSession简介

URLSession是Apple iOS 系统中的官方网络库, 第三方库例如, AFNetworking, Alamofire, 以及react-native的网络库RCTNetwork都是基于官方的URLSession.

因此弄清楚URLSession的核心架构, 对于在iOS平台做开发非常重要, 这套架构一般称为iOS URL Loading System

1. iOS URL Loading SystemHTTP关联类

网上有大量的使用URLSession发起网络请求的文章, 这里就不深入了. 这里只举几个比较有代表性的例子, 主要为了引出URLSessiohttp 404n的关联类对象:

    func testSharedURLSession() {
        //1. 创建URL
        let url = URL(string: "https://www.baidu.com/")
        //2. 构造 http request
        var request = URLRequest(url: url!)
        request.setValue("value", forHTTPHeaderField: "HeaderFiled")
        request.httpBody = Data()
        //3. 使用 http request 通过Session构造 task
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // 请求回调结果
        }
        //4. task 正式发起请求
        task.resume()
    }
    func testCustomURLSession() {
        // 1. 创建 Session 配置对象, 所有通过 Session的创建的 Task, 会有一些公用属性
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 5
        config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3", "header4": "svalue4"]
        // 2. 使用 config 创建 Session
        // 可以设置 session的 delegate 和 delegateQueue
        let session = URLSession(configuration: config, delegate: nil, delegateQueue: OperationQueue.main)
        // 3. 通过 session 创建 Task(包装Request)
        let url = URL(string: "https://www.baidu.com/")
        let request = URLRequest(url: url!)
        let task = session.dataTask(with: request) { data, response, error in
            // 请求回调结果
        }
        // 4. 真实启动任务, 发起请求
        task.resume()
    }

基本上iOS URL Lo成员变量和静态变量的区别ading system会与如下各种类型相关:

  1. HTT成员变量和局部变量区别P Request: URL + URLRequest
  2. HTTP Response: URLResponse, HTTPURLResponse
  3. Task工厂类: URLSessi缓存on + URLSessionConfiguration
  4. 不同功能的Task: URLSessionTask, URLSessionDataTask, URLSessionUploadTask,URLSessionDownloadTask
  5. 传输层关联内容: URLProtocol
  6. 缓存相关: URLCache
  7. Cookie相关: HTTPCookie, HTTPCookieStorage
  8. 鉴权相关: URLAuthenticationChallenge,URLCredential,URLCredentialStorage,URLPrhttp代理otectionSpace
  9. URLSession底层真实的网络请求库: curl

2. Task任务工厂URLSession, 以及_成员变量用于描述对象的特征TaskRegistry

在上面的Demo中能看到, 不论是使用URLSessionsh缓存是什么意思ared实例, 或者自定义创建一个URLSession的实例, 最终需要通过以下3个方法创建URLSession缓存视频怎样转入相册Task的实例:

  1. func dataTask(with ..实例化对象是什么意思.) -> URLShttp代理essionDataTa缓存视频合并appsk
  2. func uploadTask(with ...) -> URLSessionUphttpclientloadTask
  3. func downloadTask(with ...) -> U缓存的视频在哪RLSessionDownloadT实例化servlet类异常ask

简单来说, URLSession的实例, 就是一个 Tahttpwatchsk工厂, 它可以通过不同的方法, 创建不同的URLSessionTask!!!

我们可以通过实例化URLSession时, 通过URLSession成员变量和局部变量Configuration给这个工厂做一些通用配置, 这些配置可以参考URLSe成员变量用于描述对象的特征ssionConfiguration的属性和方法, 基本上有:

  1. requestChttps认证achePolicy: 工厂产生的Task关联的Request的通用Cache策略
  2. timeout: 各种timeout, 请注意, 这些timeout大多数是在URLSessionTask这个对象中缓存的视频在哪维护的
  3. httpAdditionalHeaders缓存视频合并app: 通用的HTTP Header
  4. httpCookieStorage: 统一的Cookihttp://192.168.1.1登录e管理对象
  5. urlCredentialStorage: 鉴权复用对象
  6. urlCache
  7. protocolClasses: 本地真实的数据代理类型!!!!!!

最终的Task携带的配置, 会依赖URLRequest参数结合URLSession工厂实例化servlet类异常的两者的配置而产生, URLRequest的配置会覆HTTP盖工厂的默认配置.

每个URLSession实例都有一个taskRegistry成员变量, 它是一个内部类 — _TaskReg成员变量有没有默认值istry, URLSession实例用它来持有自己生产的URLSessionTask, 具体的实现方式可以参考如下代实例化对象是什么意思码:

open class URLSession: NSObject {
    ... 
    internal let taskRegistry = URLSession._TaskRegistry() // 管理当前 Session 发起的 所有 Task
    /// 任务管理中心
    class _TaskRegistry {
        // 真实的数据业务的回调
        /// Completion handler for `URLSessionDataTask`, and `URLSessionUploadTask`.
        typealias DataTaskCompletion = (Data?, URLResponse?, Error?) -> Void
        /// Completion handler for `URLSessionDownloadTask`.
        typealias DownloadTaskCompletion = (URL?, URLResponse?, Error?) -> Void
        // 具体的 task 真实的行为 - 可能有几种!
        /// What to do upon events (such as completion) of a specific task.
        enum _Behaviour {
            /// Call the `URLSession`s delegate
            case callDelegate
            /// Default action for all events, except for completion.
            case dataCompletionHandler(DataTaskCompletion)
            /// Default action for all events, except for completion.
            case downloadCompletionHandler(DownloadTaskCompletion)
        }
        // key 是 task 的Identifier
        fileprivate var tasks: [Int: URLSessionTask] = [:]
        fileprivate var behaviours: [Int: _Behaviour] = [:]
        // TaskRegistry 完全清空以后的回调
        fileprivate var tasksFinishedCallback: (() -> Void)?
        func add(_ task: URLSessionTask, behaviour: _Behaviour) { ... }
        func remove(_ task: URLSessionTask) { ... }
        var allTasks: [URLSessionTask] {         
            return tasks.map { $0.value }
        }
    }
    ...
}  

http://www.baidu.com们知道URhttps域名LSessionTask的回调方式分成两种以下两种, 具体使用哪种也是在_TaskRegistry._Behaviour枚举中进行维护的:

  1. completionBlock
  2. delegate

那么, 这些回调方法是如何关联并管理的呢?

3. URLSession成员变量和局部变量 创建 URLSessionTask 并使用taskRegistry管理的流程

简单看一HTTPS下这个过程:

open class URLSession: NSObject {
    ...
    internal let workQueue: DispatchQueue = DispatchQueue(label: "URLSession<(identifier)>")
    /// URLSession 的关键 API
    open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
        return dataTask(with: _Request(request), behaviour: .dataCompletionHandler(completionHandler))
    }
    func dataTask(with request: _Request, behaviour: _TaskRegistry._Behaviour) -> URLSessionDataTask {
        // 1. 判断 session 状态是否 - fatalError
        guard !invalidated else {
            fatalError("Session invalidated")
        }
        // 2. 使用工厂方法配置 request
        let r = createConfiguredRequest(from: request)
        // 3. 为初始化创建 task 做准备, 构造 taskIdentifier 作为唯一id
        let i = createNextTaskIdentifier()
        // 4. task 创建方法 -> 注意 task会持有session !!!
        let task = URLSessionDataTask(session: self, request: r, taskIdentifier: i)
        // 5. taskRegistry 管理 task + behaviour
        workQueue.async {
            // 做法很简单,  task.Identifier 是key
            // 管理 tasks, behaviours
            self.taskRegistry.add(task, behaviour: behaviour)
        }
        return task
    }
    // 内置一个枚举内容 - URLSession 会关联两个内容 -> 要不直接使用 URL/Request
    enum _Request {
        case request(URLRequest)
        case url(URL)
    }
    // 通过 外部request + configure 构造真实的 URLRequest
    func createConfiguredRequest(from request: URLSession._Request) -> URLRequest {
        let r = request.createMutableURLRequest()
        // 使用内置的 configuration 配置 request
        return _configuration.configure(request: r)
    }
}

通过以上核心代码, 我们能得到如下关键信息:

  1. URLSession 拥有一个serial DispatchQueueworkQueue, 左右针对taskRegistry的操作, 都是在这个串行队列缓存文件夹名称中完成, 保证线程安全
  2. 内部拥缓存是什么意思有一个枚举类_Request, 使用它封装对外API传入的URL/URhttp协议LRequest
  3. 不论最终的回调是delegate or completionHandler, 都会在内部成员变量封装成统一的behaviour枚举
  4. URLSessionDataTask实际是通过request创建的!!!

一句话总结: URLSession 是一个生产URLSessionDataTask的工厂!!!