Syl*_*ver 36 networking ios swift alamofire
我的问题与此问题非常相似,但对于Alamofire:AFNetworking:处理全局错误并重复请求
如何能够捕获全局错误(通常是401)并在其他请求发出之前处理它(如果不管理,最终会失败)?
我正在考虑链接自定义响应处理程序,但在应用程序的每个请求上执行此操作都很愚蠢.
也许是子类化,但是我应该将哪个类子类来处理呢?
cno*_*oon 96
考虑到NSURLSessions的并行特性,在oauth流中处理401响应的刷新非常复杂.我花了很多时间构建一个对我们来说非常好的内部解决方案.以下是对如何实施的一般概念的非常高级别的提取.
import Foundation
import Alamofire
public class AuthorizationManager: Manager {
public typealias NetworkSuccessHandler = (AnyObject?) -> Void
public typealias NetworkFailureHandler = (NSHTTPURLResponse?, AnyObject?, NSError) -> Void
private typealias CachedTask = (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void
private var cachedTasks = Array<CachedTask>()
private var isRefreshing = false
public func startRequest(
method method: Alamofire.Method,
URLString: URLStringConvertible,
parameters: [String: AnyObject]?,
encoding: ParameterEncoding,
success: NetworkSuccessHandler?,
failure: NetworkFailureHandler?) -> Request?
{
let cachedTask: CachedTask = { [weak self] URLResponse, data, error in
guard let strongSelf = self else { return }
if let error = error {
failure?(URLResponse, data, error)
} else {
strongSelf.startRequest(
method: method,
URLString: URLString,
parameters: parameters,
encoding: encoding,
success: success,
failure: failure
)
}
}
if self.isRefreshing {
self.cachedTasks.append(cachedTask)
return nil
}
// Append your auth tokens here to your parameters
let request = self.request(method, URLString, parameters: parameters, encoding: encoding)
request.response { [weak self] request, response, data, error in
guard let strongSelf = self else { return }
if let response = response where response.statusCode == 401 {
strongSelf.cachedTasks.append(cachedTask)
strongSelf.refreshTokens()
return
}
if let error = error {
failure?(response, data, error)
} else {
success?(data)
}
}
return request
}
func refreshTokens() {
self.isRefreshing = true
// Make the refresh call and run the following in the success closure to restart the cached tasks
let cachedTaskCopy = self.cachedTasks
self.cachedTasks.removeAll()
cachedTaskCopy.map { $0(nil, nil, nil) }
self.isRefreshing = false
}
}
Run Code Online (Sandbox Code Playgroud)
这里要记住的最重要的事情是,您不希望为每个返回的401运行刷新调用.大量请求可以同时竞赛.因此,您希望对第一个401执行操作,并将所有其他请求排入队列,直到401成功为止.我上面概述的解决方案确实如此.通过该startRequest方法启动的任何数据任务将在达到401时自动刷新.
此处需要注意的其他一些重要事项在这个非常简单的示例中没有考虑到:
希望这有助于解决问题.
现在,我们已经发布了Alamofire 4.0它添加RequestAdapter和RequestRetrier协议,可以让您轻松不管授权实施细则建立自己的认证系统!有关更多信息,请参阅我们的自述文件,其中有一个完整的示例,说明如何在OAuth2系统上实现您的应用程序.
完全披露: README中的示例仅用作示例.请不要只是将代码复制粘贴到生产应用程序中.
在 Alamofire 5 中,您可以使用 RequestInterceptor 这是我在我的一个项目中对 401 错误的错误处理,我将 EnvironmentInterceptor 传递给它的每个请求,如果请求出错,将调用重试函数,并且适配函数也可以提供帮助您为您的请求添加默认值
struct EnvironmentInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (AFResult<URLRequest>) -> Void) {
var adaptedRequest = urlRequest
guard let token = KeychainWrapper.standard.string(forKey: KeychainsKeys.token.rawValue) else {
completion(.success(adaptedRequest))
return
}
adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: HTTPHeaderField.authentication.rawValue)
completion(.success(adaptedRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
//get token
guard let refreshToken = KeychainWrapper.standard.string(forKey: KeychainsKeys.refreshToken.rawValue) else {
completion(.doNotRetryWithError(error))
return
}
APIDriverAcountClient.refreshToken(refreshToken: refreshToken) { res in
switch res {
case .success(let response):
let saveAccessToken: Bool = KeychainWrapper.standard.set(response.accessToken, forKey: KeychainsKeys.token.rawValue)
let saveRefreshToken: Bool = KeychainWrapper.standard.set(response.refreshToken, forKey: KeychainsKeys.refreshToken.rawValue)
let saveUserId: Bool = KeychainWrapper.standard.set(response.userId, forKey: KeychainsKeys.uId.rawValue)
print("is accesstoken saved ?: \(saveAccessToken)")
print("is refreshToken saved ?: \(saveRefreshToken)")
print("is userID saved ?: \(saveUserId)")
completion(.retry)
break
case .failure(let err):
//TODO logout
break
}
}
} else {
completion(.doNotRetry)
}
}
Run Code Online (Sandbox Code Playgroud)
你可以像这样使用它:
@discardableResult
private static func performRequest<T: Decodable>(route: ApiDriverTrip, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (AFResult<T>)->Void) -> DataRequest {
return AF.request(route, interceptor: EnvironmentInterceptor())
.responseDecodable (decoder: decoder){ (response: DataResponse<T>) in
completion(response.result)
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13733 次 |
| 最近记录: |