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)
}
}
函数的最开始, 定义了一个闭包, 用来结束这个请求.
接下来, 获取对应的 URLSessionTask
与 SessionManager
.
接下来, 调用所有的校验器校验. 如果发生了错误, 但是有重试器的情况, 可以尝试重试,
所有过程结束之后, 调用结束闭包结束
这里出现了一个新的东西, 检验器
检验器 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 里面定义了三种可能会失败的情况
- 没有 response
- 校验之前就已经产生错误了
- 检验结果为失败
失败之后, 将 error 保存起来, 晕了的同学, 提示一下, 这里的 self 是 Requst
, self.delegate
是 TaskDelegate
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()
}
难道说, 就只做这个事? 当然不是