1. 应用场景
发送网络请求时,一些请求失败后需要自动重试。大多数情况下,在重试前需要做一些操作,比如更新参数,更新 token 等。
本文对应的真实场景: 发送请求 -> 返回401 -> 更新 token -> 重新请求
如果不知道 Alamofire 提供了 retrier 机制,你很有可能想到在每一个请求的失败返回中手动重试,千万不要。下面详细介绍怎样利用 Alamofire 便捷地实现重试。
2. Alamofire 的 RequestInterceptor
RequestInterceptor 是一个协议,其中包含了 RequestAdapter 和 RequestRetrier 两个协议。
2.1 RequestAdapter
暂且翻译成”适配器“吧。
这个协议只有一个函数:
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
它可以让你在请求真正发起前,做一些预处理。比如给某些特定函数做重定向、添加一些 header,或者阻拦某些请求。
本文真实场景:给一个特定请求添加一个用于身份验证的 token。
2.1 RequestRetrier
翻译成”重试器“吧。
这个协议也只有一个函数:
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
实现这个函数的时候,可以通过执行函数的 completion 闭包来决定是否需要重试。
所以,在这个函数实现中,我们只要去判断出需要重试的条件,然后执行 completion(.retry),一旦请求有错误返回,就会进入这个函数,重试就触发了。
3. 代码示例
// 新建一个自定义Interceptor, 遵从 RequestInterceptor 协议
class GenericRequestInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var request = urlRequest
//Just for gatway host
guard let baseUrl = gatewayBaseUrl, !accessToken.isEmpty else {
return completion(.success(request))
}
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseUrl) {
// set JWT token to request header
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
}
completion(.success(request))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
completion(.doNotRetry)
return
}
// 确保只重试一次,否则就无限重试下去了
guard request.retryCount == 0, let baseUrl = gatewayBaseUrl else { return completion(.doNotRetry) }
// 如果是特定 URL 并且状态码是 401
if let urlString = request.firstRequest?.url?.absoluteString, urlString.hasPrefix(baseUrl) {
// 重新获取 token
gatewayAuthService?.refreshJWT(createURLRequest: createURLRequestFunc, complete: {[weak self] accessToken in
if !accessToken.isEmpty {
// 保存新的 token,重试的时候在 adapter 中使用
self?.accessToken = accessToken
// 重试
completion(.retry)
} else {
completion(.doNotRetry)
}
})
} else {
completion(.doNotRetry)
}
}
}
然后在初始化 Session 的时候, 把这个自定义 GenericRequestInterceptor 设置给 Session。
sessionManager = Session(configuration: sessionConfig, interceptor: GenericRequestInterceptor())
这样,如果请求失败,就会触发重试。
4. 小心有坑
本文肯定不仅仅是一个基础教程啦,否则就没意思了。
且看!
如果你发起的是一个错误请求,比如 “https://www.balabalabala.com”, 很快会进入 retrier 函数。
但是,如果请求返回诸如 401 的状态码,它是不会进入 retrier 函数滴。为什么呢?
这是 Alamofire 文档里的一句话:
Response Validation
By default, Alamofire treats any completed request to be successful, regardless of the content of the response.
意思是,像 401 这种返回,按照请求成功处理。所以不会触发 Retrier.
那怎么办呢?
validate()
这个东西可以让你在 response 之前,对特定状态码返回错误。就是你可以在这个里面让 401 当做错误返回。
return sessionManager.request(urlRequest).validate({ request, response, data in
let statusCode = response.statusCode
if statusCode != 401 {
return .success(())
} else {
return .failure(AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: 401)))
}
}) .response(queue: workerQueue, completionHandler: { [weak self] dataResponse in
}
这样,遇到 401 就可以进入 Retrier 了。