私の質問はこれとよく似ていますが、Alamofireの場合: AFNetworking:エラーをグローバルに処理し、リクエストを繰り返します
グローバルにエラー(通常401)をキャッチし、他の要求が行われる前に処理する方法(管理されない場合は最終的に失敗する方法)
私はカスタム応答ハンドラーをチェーン化することを考えていましたが、アプリの各リクエストでそれを行うのはばかげています。
サブクラス化の可能性がありますが、それを処理するにはどのクラスをサブクラス化する必要がありますか?
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
}
}
ここで覚えておくべき最も重要なことは、戻ってくる401ごとに更新呼び出しを実行したくないということです。多数のリクエストが同時に競合する可能性があります。したがって、最初の401でアクションを実行し、401が成功するまですべての追加リクエストをキューに入れる必要があります。私が上で概説した解決策はまさにそれをします。 startRequest
メソッドを介して開始されたデータタスクは、401に到達すると自動的に更新されます。
この非常に単純化された例で説明されていない、ここで注意すべきその他の重要な事項は次のとおりです。
うまくいけば、これが光を当てる助けになります。
私たちは今リリースしました???????? Alamofire 4.0 ???????? RequestAdapter
およびRequestRetrier
プロトコルが追加され、承認実装の詳細に関係なく独自の認証システムを簡単に構築できます。詳細については、 [〜#〜] readme [〜#〜] を参照してください。OAuth2システムでアプリに実装する方法の完全な例があります。
完全開示:READMEの例は、例としてのみ使用することを意図しています。コードをコピーして本番アプリケーションに貼り付けてください。
alamofire 5ではRequestInterceptorを使用できますここで私のプロジェクトの401エラーのエラー処理がありますリクエストにデフォルト値を追加します
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)
}
}
次のように使用できます:
@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)
}