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

2017-10-12  本文已影响169人  yww

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

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

上一篇 下一篇

猜你喜欢

热点阅读