Alamofire Response源码解读

前言

Alamofire设计了2种与Request相对应的Response类型,他们分别是:

  • DefaultDataResponse / DataResponse -- >DataRequest,UploadRequest
  • DefaultDownloadResponse / DataResponse --> DownloadRequest
  • 如果使用了没有序列化的response方法,返回的就是带有Default开头的响应者,比如DefaultDataResponseDefaultDownloadResponse
  • 如果使用了序列化的response方法,返回的就是DataResponse或者DataResponse
  • DefaultDataResponse / DataResponse来举例,DataResponse基本上只比DefaultDataResponse多了一个系列化后的数据属性。

Response流程分析

先看一段简单代码

SessionManager.default
    .request(urlString)
    .response { (response) in
        print(response)
    }
    .responseJSON { (jsonResponse) in
        print(jsonResponse)
}
  • 因为Alamofire是采用的链式调用设计,所以可以在调用response后还能继续调用responseJSON。能实现链式访问的原理就是每个函数的返回值都是Self
  • 在上面的代码中,先调用了request,再调用了response。那么Alamofire是怎么保证response的的执行时机是在request发起请求并在数据回调之后再执行的呢?这里一定有猫腻,要不然只要调用了response方法就会里面执行里面的代码。进入到response方法里看看:
public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
    delegate.queue.addOperation {
        (queue ?? DispatchQueue.main).async {
            var dataResponse = DefaultDataResponse(
                request: self.request,
                response: self.response,
                data: self.delegate.data,
                error: self.delegate.error,
                timeline: self.timeline
            )

            dataResponse.add(self.delegate.metrics)

            completionHandler(dataResponse)
        }
    }

    return self
}
  • response加到了一个队列queue
self.queue = {
    let operationQueue = OperationQueue()

    operationQueue.maxConcurrentOperationCount = 1
    operationQueue.isSuspended = true
    operationQueue.qualityOfService = .utility

    return operationQueue
}()
  • operationQueue.maxConcurrentOperationCount = 1说明是一个串行队列
  • operationQueue.isSuspended = true队列默认是挂起状态
  • 相信看到这里就能够猜到具体是怎么实现的了。
    1. 调用response方法时把任务加到队列queue
    2. queue是一个串行队列,并且默认是挂起状态,所以先不执行任务
    3. 在请求完成的代理回调方法中把queue.isSuspended = false,开始执行队列中的任务。
  • 找到请求完成的代理回调发现确实如上所说。
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let taskDidCompleteWithError = taskDidCompleteWithError {
        taskDidCompleteWithError(session, task, error)
    } else {
        // 省略无关代码......
        queue.isSuspended = false
    }
}

DefaultDataResponse

把目光移到response方法,创建了一个DefaultDataResponse对象并作为completionHandler的参数回调出去。

public struct DefaultDataResponse {
    /// 表示该响应来源于那个请求
    public let request: URLRequest?

    /// 服务器返回的响应
    public let response: HTTPURLResponse?

    /// 响应数据
    public let data: Data?

    /// 在请求中可能发生的错误
    public let error: Error?

    /// 请求的时间线封装
    public let timeline: Timeline
    
    /// 包含了请求和响应的统计信息
    var _metrics: AnyObject?
}
  • DefaultDataResponse是一个结构体类型,用来保存数据。一般来说,在Swift中,如果只是为了保存数据,那么应该把这个类设计成structstruct是值传递,因此对数据的操作更安全。
  • 把所有关于Response的数据全部保存在DefaultDataResponse结构体中,化零为整,很好的一个面向对象的设计原则,把这个完整的数据返回给用户,用户想用什么就取什么。
  • 我们来重点看下保存在DefaultDataResponse中的data是如何来的。继续来到response方法中:
var dataResponse = DefaultDataResponse(
    request: self.request,
    response: self.response,
    data: self.delegate.data,
    error: self.delegate.error,
    timeline: self.timeline
)
  • 点进去找到DataTaskDelegate里的data,发现其实返回的是mutableData,那么mutableData又是什么呢?
override var data: Data? {
    if dataStream != nil {
        return nil
    } else {
        return mutableData
    }
}
  • 在当前文件中搜索mutableData发现:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }

    if let dataTaskDidReceiveData = dataTaskDidReceiveData {
        dataTaskDidReceiveData(session, dataTask, data)
    } else {
        if let dataStream = dataStream {
            dataStream(data)
        } else {
            mutableData.append(data)
        }

        // 省略无关代码......
    }
}
  • 在接收数据的代理方法中,把接收到的数据拼接在mutableData中。

DataResponse<Value>

DataResponse<Value>比上边的DefaultDataResponse多了一个public let result: Result<Value>属性,该属性存储了序列化后的数据。接着看看在Alamofire中是如何使用Result的,来到responseJSON方法中:

public func responseJSON(
    queue: DispatchQueue? = nil,
    options: JSONSerialization.ReadingOptions = .allowFragments,
    completionHandler: @escaping (DataResponse<Any>) -> Void)
    -> Self
{
    return response(
        queue: queue,
        responseSerializer: DataRequest.jsonResponseSerializer(options: options),
        completionHandler: completionHandler
    )
}
  • 上边的这个函数的主要目的是把请求成功后的结果序列化为JSON再返回,completionHandler函数的参数类型为DataResponse<Any>,其中的Any就会传递给Result,也就是Result<Any>

DataResponseSerializer

一般来说,我们需要对response.data做序列化处理之后才方便使用。Alamofire已经提供了一些常用的序列化器,可以直接调用api使用。同样也可以自定义序列化器来实现自己功能。下面来看看responseJSON方法时怎么实现的。

public func responseJSON(
    queue: DispatchQueue? = nil,
    options: JSONSerialization.ReadingOptions = .allowFragments,
    completionHandler: @escaping (DataResponse<Any>) -> Void)
    -> Self
{
    return response(
        queue: queue,
        responseSerializer: DataRequest.jsonResponseSerializer(options: options),
        completionHandler: completionHandler
    )
}
  • 其实内部也是调用的response方法,只是传了一个json序列化器作为参数
public static func jsonResponseSerializer(
    options: JSONSerialization.ReadingOptions = .allowFragments)
    -> DataResponseSerializer<Any>
{
    return DataResponseSerializer { _, response, data, error in
        return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
    }
}
  • 初始化了一个DataResponseSerializer结构体,并且保存了一个尾随闭包serializeResponse
  • 通过前面对Response流程分析可以知道,当请求完成回调之后代码会执行到response方法中加入到队列的任务。也就会调用上面的尾随闭包。
  • 点击进入到Request.serializeResponseJSON方法
public static func serializeResponseJSON(
    options: JSONSerialization.ReadingOptions,
    response: HTTPURLResponse?,
    data: Data?,
    error: Error?)
    -> Result<Any>
{
    guard error == nil else { return .failure(error!) }

    if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }

    guard let validData = data, validData.count > 0 else {
        return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
    }

    do {
        let json = try JSONSerialization.jsonObject(with: validData, options: options)
        return .success(json)
    } catch {
        return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
    }
}
  • 返回的是一个Result<Any>类型的枚举,如果有错误信息,就返回return .failure(error!)并把错误信息做完枚举的关联值返回出去。
  • 如果成功就把结果序列化之后返回出去return .success(json)
  • 所以可以在处理请求结果时,可以直接通过dataResponse.result来判断请求结果成功还是失败。

总结

  • response方法的响应结果回调completionHandler(dataResponse)加入到一个串联队列,并且这个队列是默认挂起的,当请求完成后队列开始执行,这样才能保证是在请求完成之后再回调结果。
  • 可以自定义系列化器,只需要自定义的序列化器实现DataResponseSerializerProtocol这个协议就可以。
  • DataResponse帮助我们统一管理请求过程中的数据,请求成功、失败、时间轴等等,便于业务层处理。

有问题或者建议和意见,欢迎大家评论或者私信。
喜欢的朋友可以点下关注和喜欢,后续会持续更新文章。

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

推荐阅读更多精彩内容