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

Alamofire.request

request 函数签名

request 函数实现

SessionManager.default

SessionManager.default.request

接收响应

请求既然已经发出去了, 那么响应又是如何接收的呢? 数据又是如何保存的?
我们之前设置过 SessionDelegate, 所以, 响应肯定是从这里得到通知的.

接收数据中

首先, 我们会就收到数据, 回调函数会被调用

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)
    }
}

由于我们这里没有设置自定义接收事件的回调, 所以, 这里就去调用 TaskDelegate 的函数完成接收数据.
这里面同样有一些在自定义了回调时会调用的闭包, 为了方便查看, 这里直接删掉部分代码

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
    mutableData.append(data)
    let bytesReceived = Int64(data.count)
    totalBytesReceived += bytesReceived
    let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
    progress.totalUnitCount = totalBytesExpected
    progress.completedUnitCount = totalBytesReceived
    if let progressHandler = progressHandler {
        progressHandler.queue.async { progressHandler.closure(self.progress) }
    }
}

可以看到, 首先也是记录一下接收到请求的时间, 然后保存数据, 记录一下进度, 最后, 如果有进度的监控, 那么通知一下即可

请求完成

当请求完成后, 有两种情况, 一种是接收数据, 请求成功了, 第二种则是中途发生了错误.
但是并不是一定说接收到数据就一定是请求成功了, 例如登录的时候, 用户名和密码不匹配, 导致登录失败. 所以这里稍微有些判断.
和刚才一样, 也是由 SessionDelegate 接收事件

open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
        guard let strongSelf = self else { return }
        strongSelf.taskDidComplete?(session, task, error)
        strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)
        NotificationCenter.default.post(
            name: Notification.Name.Task.DidComplete,
            object: strongSelf,
            userInfo: [Notification.Key.Task: task]
        )
        strongSelf[task] = nil
    }
    guard let request = self[task], let sessionManager = sessionManager else {
        completeTask(session, task, error)
        return
    }
    request.validations.forEach { $0() }
    var error: Error? = error
    if request.delegate.error != nil {
        error = request.delegate.error
    }
    if let retrier = retrier, let error = error {
        retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
            guard shouldRetry else { completeTask(session, task, error) ; return }
            DispatchQueue.utility.after(timeDelay) { [weak self] in
                guard let strongSelf = self else { return }
                let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false
                if retrySucceeded, let task = request.task {
                    strongSelf[task] = request
                    return
                } else {
                    completeTask(session, task, error)
                }
            }
        }
    } else {
        completeTask(session, task, error)
    }
}

函数的最开始, 定义了一个闭包, 用来结束这个请求.
接下来, 获取对应的 URLSessionTaskSessionManager.
接下来, 调用所有的校验器校验. 如果发生了错误, 但是有重试器的情况, 可以尝试重试,
所有过程结束之后, 调用结束闭包结束
这里出现了一个新的东西, 检验器

检验器 validate

例如我们要检验 http 状态码:

Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200 ..< 300)
.responseString { (response) in
    if let string = response.result.value {
        print(string)
    }
}

我们来看看校验器的代码

extension Request {
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
        return validate { [unowned self] _, response, _ in
            return self.validate(statusCode: acceptableStatusCodes, response: response)
        }
    }
}

这个函数参数看起来略微有点复杂, 其实就是一个整数类型的序列
返回值是自身类型(如 DataRequest)(这样的目的是为了链式调用, 也就是说你可以同时挂上很多个校验器)
函数体里面, 调用了另一个函数, 函数参数是一个闭包, 闭包有返回值, 返回值是一个 ValidationResult
这个函数定义如下

public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
public func validate(_ validation: @escaping Validation) -> Self {
    let validationExecution: () -> Void = { [unowned self] in
        if let response = self.response,
            self.delegate.error == nil,
            case let .failure(error) = validation(self.request, response, self.delegate.data) {
            self.delegate.error = error
        }
    }
    validations.append(validationExecution)
    return self
}

这个函数里面有一个闭包, 闭包里面是一个校验块.
后面使用validations.append(validationExecution) 存储起来, 供后续校验,
我们来看这个校验块
里面只有一个 if 语句, if 里面定义了三种可能会失败的情况

  1. 没有 response
  2. 校验之前就已经产生错误了
  3. 检验结果为失败

失败之后, 将 error 保存起来, 晕了的同学, 提示一下, 这里的 self 是 Requst, self.delegateTaskDelegate
ValidationResult 定义如下, 这个类型是为了存储校验的结果.

public enum ValidationResult {
    case success
    case failure(Error)
}

回过头来继续看之前的校验状态码函数, 里面调用的是另一个 validate 函数, 这个函数返回的就是 ValidationResult

extension Request {
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
        return validate { [unowned self] _, response, _ in
            return self.validate(statusCode: acceptableStatusCodes, response: response)
        }
    }
    fileprivate func validate<S: Sequence>(
        statusCode acceptableStatusCodes: S,
        response: HTTPURLResponse)
        -> ValidationResult
        where S.Iterator.Element == Int
    {
        if acceptableStatusCodes.contains(response.statusCode) {
            return .success
        } else {
            let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
            return .failure(AFError.responseValidationFailed(reason: reason))
        }
    }
}

里面逻辑很简单, 就是判断是否包含了指定的状态码, 如果包含则成功, 否则失败. 这里有三个 validate 函数, 稍微有点绕.
我们可以尝试自己写一个验证函数, 例如我们规定, 返回内容不能为空, 否则抛出错误

Alamofire.request("https://httpbin.org/status/200")
    .validate(statusCode: 200 ..< 300)
    .validate({ (_, _, data) -> Request.ValidationResult in
        if data == nil  {
            return .success
        }
        return .failure(AFError.responseValidationFailed(reason:.dataFileNil))
    })
    .responseString { (response) in
        if let string = response.result.value {
            print(string)
        } else if let error = response.result.error {
            print(error)
        }
}

除了校验器之外, 这里还有一个没见过的 重试器 retrier

重试器 retrier

重试器可以在请求失败时重试请求, 常见的应用场景如token 过期, 我们可能会需要去重新登录一遍
重试器本身是一个协议.

public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
public protocol RequestRetrier {
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}

这个协议中, 只有一个方法.
最后一个参数时当你的决定是否需要重试时调用的回调函数.
例如, 你可能需要在 Token 过期时, 再度请求服务器尝试登陆, 当登陆成功返回正常的 Token 后, 你可能需要重试一遍, 否则, 你可能会想要终止当前的请求.
例如:

class AutoLogin: RequestRetrier {
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        if request.response?.statusCode == 401 {
            manager.request("someloginapi").responseString(completionHandler: { (response) in
                if let newToken = response.result.value {
                    // save token
                    print(newToken)
                    completion(true, 0)
                } else {
                    completion(false, 0)
                }
            })
        } else {
            completion(false, 0)
        }
    }
}
let manager = Alamofire.SessionManager()
manager.retrier = AutoLogin()
manager.request("someApi").responseString { (response) in
    response.result.value.map({ (value) -> Void in
        print(value)
    })
}

不过最妙的还是要和 Adapter 结合起来, 一边在请求中添加 Token, 同时在登陆过期时, 自动重试, 简直完美.
这里附上 Alamofire 的例子, 相信对你会有启发的
https://github.com/Alamofire/Alamofire#requestretrier
这里我们又要继续回头了, 我们继续去看请求完成的回调函数
这里有个闭包. 晕了的同学, 给个路标, 我们现在还在 SessionDelegate 里面.

let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
    guard let strongSelf = self else { return }
    strongSelf.taskDidComplete?(session, task, error)
    strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)
    NotificationCenter.default.post(
        name: Notification.Name.Task.DidComplete,
        object: strongSelf,
        userInfo: [Notification.Key.Task: task]
    )
    strongSelf[task] = nil
}

这里可以看出来, 流程首先是调用自己的一个闭包属性, 以通知任务已经结束, 然后调用 TaskDelegate 的请求结束函数. 最后发送通知, 并清理 Request 的绑定.

TaskDelegate 中的结束请求

上面跟着 SessionDelegate 一路到了这里. 先看看代码的实现

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  if let taskDidCompleteWithError = taskDidCompleteWithError {
      taskDidCompleteWithError(session, task, error)
  } else {
      if let error = error {
          if self.error == nil { self.error = error }
          if let downloadDelegate = self as? DownloadTaskDelegate,
              let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
              downloadDelegate.resumeData = resumeData
          }
      }
      queue.isSuspended = false
  }
}

抛去自定义的处理, 与错误处理不看, 其实就一句

queue.isSuspended = false

这个队列, 我们在上一篇中, DataTask 构造函数中看到过, 当时里面添加了一个任务, 在请求结束时, 记录结束的时间

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

难道说, 就只做这个事? 当然不是

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

推荐阅读更多精彩内容