Alamofire-从一个简单的请求深入源代码(3)

Alamofire.request

request 函数签名

request 函数实现

SessionManager.default

SessionManager.default.request

之前我们写的 Alamofire.request 最终就是调用到这里来了, 现在终于可以看看这里到底做了些什么了

open func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    var originalRequest: URLRequest?

    do {
        // 根据 url , header 生成请求
        originalRequest = try URLRequest(url: url, method: method, headers: headers)
        // 编码进去参数
        let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

可以看到, 这里调用了我们之前介绍过的 ParameterEncoding 中的 encoding 方法编码参数.
这里利用 url 等相关信息, 生成了一个 URLRequest 对象, 再利用它去请求数据

另一个 request 函数

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
  ...
}

抛开函数体先不谈, 这个函数的参数跟 URLConvertible 也是一样, 是一个协议. 想必你已经猜出来了. 这个协议一定是为了将对象转换成URLRequest
接下来, 看看函数体
敲黑板, 重点来了

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    var originalRequest: URLRequest?
    do {
        originalRequest = try urlRequest.asURLRequest()
        let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
        let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
        let request = DataRequest(session: session, requestTask: .data(originalTask, task))
        delegate[task] = request
        if startRequestsImmediately { request.resume() }
        return request
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

这里出现了很多新的类型, 先一步一步分析

originalRequest = try urlRequest.asURLRequest()

这一步, 只是为了获取 URLRequest 对象而已, 当然, 有可能会出错, 所以有 try 语句.
接下来这一句

let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)

RequestableDataRequest 中的的一个内部类型, DataRequest 我们先放一放, 先看看Requestable

Requestable

这个类型是一个结构体, 代码很少, 也很简单

struct Requestable: TaskConvertible {
    let urlRequest: URLRequest

    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
}

这个结构体实现了一个 TaskConvertible 协议, 想必你也猜出来了, 这个协议有一个生成 URLSessionTask 的方法.
结构体中, 除了那个方法之外, 还有一个 urlRequest 属性, 用于保存对应的 URLRequest, 之前的调用的构造函数也是为了初始化这个属性.
task 函数中, 除了必要的 session 参数之外, 还有一个 RequestAdapterDispatchQueue
RequestAdapter 可以在创建URLSessionTask 之前, 修改URLRequest 对象, 这是一个很有用的东西, 你可以拿这个做很多事情, 比如, 加上一个 token, 或是一个授权码等等.
RequestAdapter 本身是一个协议, 只有一个方法, 实现起来也超级容易

public protocol RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}

如果你自定义了一个 Adapter, 要如何使用呢?
很简单, 赋值到 SessionManager 里的 adapter 属性就好了, 例如

class TokenAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest
        urlRequest.addValue("some token", forHTTPHeaderField: "ACCESS-TOKEN")
        return urlRequest
    }
}
...
// 由于要修改 sessionManager, 所以这里就不能直接使用 request 方法了, 我们需要自己创建一个 sessionManager
let sessionManager = Alamofire.SessionManager.default
// 设置 adapter
sessionManager.adapter = TokenAdapter()
// 使用这个 sessionManager 发起请求
sessionManager.request("https://httpbin.org/get").responseString { (response) in
    if let string = response.result.value {
        print("alamofire with adapter", string)
    }
}

而另一个参数, queue, 这里调用方式是, queue.sync 同步执行. 在这里主要作用是保证同一时间只会同时创建一个 URLSessionTask, queue 本身也是一个串行的队列
可以在 SessionManager 里面看到定义

open class SessionManager {
    ...
    let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
    ...
}

继续回到 reqeust 函数中, 下一句

let task = try originalTask.task(session: session, adapter: adapter, queue: queue)

这一句, 也就是生成 URLSessionTask, 跟我们写原声代码的这一句是一样的.

let dataTask = session.dataTask(with: URL(string: "https://httpbin.org/get")!)

接下来继续看下一句代码

let request = DataRequest(session: session, requestTask: .data(originalTask, task))

这里使用了一个 DataRequest 类型.

DataRequest

DataRequest 负责发送请求, 接收响应, 并在内部管理着 URLSessionTask
DataRequest 继承自 Request, 除了这一个子类之外, 常用的还有下载用的请求 DownloadRequest, 上传请求 UploadRequest.
父类中有一个内部类型RequestTask 用于区别这几种不同的请求

enum RequestTask {
    case data(TaskConvertible?, URLSessionTask?)
    case download(TaskConvertible?, URLSessionTask?)
    case upload(TaskConvertible?, URLSessionTask?)
    case stream(TaskConvertible?, URLSessionTask?)
}

而上面调用的构造函数中, 第二个参数就是这个枚举
.data(originalTask, task)则表示我们要初始化的是一个数据类型的请求, 来看看这个构造函数

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
   self.session = session
   switch requestTask {
   case .data(let originalTask, let task):
       taskDelegate = DataTaskDelegate(task: task)
       self.originalTask = originalTask
       ... 其他类型请求
   }
   delegate.error = error
   delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}

可以大致看出来, 首先是根据请求的类型以及 task 参数(URLSessionTask 类型) , 生成了一个 DataTaskDelegate,
并将originalTask(TaskConvertible 类型) 保存起来了, 这个是为了后面如果需要, 例如网络错误, 需要重试, 可以重新生成一个 URLSessionTask
接下来, 如果有错误, 将错误保存在其中, 并添加了一个操作.
看起来有点复杂, 我们一点一点分解.

两个代理对象taskDelegate 与 delegate

其实这两个都是 SessionManager 里面的属性, 都是指向的同一个对象, 不过 delegate 使用起来是线程安全的
定义如下

private var taskDelegate: TaskDelegate
private var taskDelegateLock = NSLock()
open internal(set) var delegate: TaskDelegate {
    get {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        return taskDelegate
    }
    set {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        taskDelegate = newValue
    }
}

DataTask设置Delegate

TaskDelegate 类型主要作用之一就是管理与 URLSessionTask 关联的回调.
看到这里, 你可能会感到疑惑, URLSessionTask 并不能设置回调, 唯一获取事件回调的地方只有一个, 就是我们最初设置的 SessionDelegate(). 但是 SessionDelegate 内部将与 URLSessionTask 关联的任务又重新分发出来了, 所以, 这里的 TaskDelegate 才能接收到事件

SessionDelegate

SessionDelegate 中实现了所有URLSessionDelegate及子协议的方法, 如URLSessionTaskDelegate, URLSessionDataDelegate, 并且以闭包的形式暴露出来, 我们这里截取部分代码略微说明一下

open class SessionDelegate: NSObject {
    open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?

    weak var sessionManager: SessionManager?
    private var requests: [Int: Request] = [:]
    private let lock = NSLock()
    open subscript(task: URLSessionTask) -> Request? {
        get {
            lock.lock() ; defer { lock.unlock() }
            return requests[task.taskIdentifier]
        }
        set {
            lock.lock() ; defer { lock.unlock() }
            requests[task.taskIdentifier] = newValue
        }
    }
}
extension SessionDelegate: URLSessionDataDelegate {
    open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        if let dataTaskDidReceiveData = dataTaskDidReceiveData {
            dataTaskDidReceiveData(session, dataTask, data)
        } else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {
            delegate.urlSession(session, dataTask: dataTask, didReceive: data)
        }
    }
}

我们以dataTaskDidReceiveData 这个回调为例.
为了能够让 TaskDelegate 能够接受到事件. 我们需要做以下几件事

  1. 创建一个 SessionDelegate 用于接收所有事件
  2. 将请求的 URlSessionTaskTaskDelegate 以某种方式绑定起来
  3. 产生事件后, 通过与事件关联的 URlSessionTask 找到 TaskDelegate并执行对应的函数

第一步我们已经在最开始创建 URLSession 的时候做了.
第二步, 由于我们这里的 TaskDelegate 都是与 Request类一一对应, 所以, 我们在 SessionDelegate 中通过下标写入的方式就可以把 URlSessionTaskTaskDelegate 绑定起来. 如以下代码

someSessionDelegate[someUrlSessionTask] = SomeRequest

第三步, 我们可以在 SessionDelegate 中扩展URLSessionDataDelegate部分看到.
如果用户没有手动的去实现SessionDelegate 的对应属性, 那么就会自动去找对应的Request, 然后获取内部的 TaskDelegate, 最后, 调用 TaskDelegate 中对应的方法.

TaskDelegate 除了接收事件外, 还有一个很大的功能. 可以在任务完成之后, 完成一些任务. 我们继续回到Request 类的构造函数中, 有一句这样一句代码

delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }

这句代码就会在任务请求结束之后执行, 并将请求结束的实际记录下来.
我们来看看 TaskDelegate

TaskDelegate
open class TaskDelegate: NSObject {
    open let queue: OperationQueue
    private var _task: URLSessionTask? {
        didSet { reset() }
    }
    init(task: URLSessionTask?) {
        _task = task
        self.queue = {
            let operationQueue = OperationQueue()
            operationQueue.maxConcurrentOperationCount = 1
            operationQueue.isSuspended = true
            operationQueue.qualityOfService = .utility
            return operationQueue
        }()
    }
    ...
}

可以看到, TaskDelegate 在构造的时候, 内部除了管理
URLSessionTask 之外 还维护了一个任务队列, 一开始是暂停的, 当请求结束时, 就会恢复. 这样也就可以在请求执行完毕时, 执行某些逻辑. 除这里的记录时间外, 还可以做很多事情, 例如将返回结果转换为一个字符串.
现在我们对 DataRequest 的构造过程(其实是父类Request) 有一个理解.
接下来我们继续回到 之前的 request 函数中, 创建好 DataRequest 后, 我们就需要将其与 SessionDelegate 绑定起来

// 有点晕了?这里的 delegate 是 SessionDelegte 类型的
delegate[task] = request

接下来, 我们就开始发起请求了

if startRequestsImmediately { request.resume() }

由于 startRequestsImmediately 的默认值是 true

open class SessionManager {
    ...
    open var startRequestsImmediately: Bool = true
    ...
}

所以我们这里就立即开始发起请求了.

发起请求 request.resume()

终于, 我们将请求发送出去了, 这里调用的依然是 Request 类中的方法, 我们看一下实现

 open func resume() {
    guard let task = task else { 
        delegate.queue.isSuspended = false
        return
     }
    if startTime == nil {
        startTime = CFAbsoluteTimeGetCurrent() 
    }
    task.resume()
    NotificationCenter.default.post(
        name: Notification.Name.Task.DidResume,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

其实也就是简单的几步, 首先, 获取 URLSessionTask, 记录开始时间, 发起请求, 发送通知.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容