私はかなり一般的であるべきユースケースを持っていますが、AFNetworkingでそれを処理する簡単な方法を見つけることができません。
サーバーがanyリクエストに対して特定のステータスコードを返すときはいつでも、次のことを実行します。
これはAFHTTPClient
のグローバルな完了/エラーハンドラーを介して実行できると思いましたが、何も役に立たなかったようです。それで、私が望むことをするための「正しい」方法は何ですか?上書きenqueueHTTPRequestOperation:
AFHTTPClient
サブクラスで、操作をコピーし、元の完了ハンドラーを、私がやりたいことを行うブロックでラップします(再認証、コピーされた操作のエンキュー)?それとも私は完全に間違ったトラックにいますか?
ありがとう!
編集:401ステータスコードへの参照を削除しました。これは、トークン認証を使用している間はおそらくHTTPベーシック用に予約されているためです。
AFHTTPClientのinitメソッドで、AFNetworkingOperationDidFinishNotification
を登録します。これは、リクエストの完了後にポストされます。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(HTTPOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];
通知ハンドラで、ステータスコードとcopy
AFHTTPRequestOperation
を確認するか、新しいコードを作成します。
- (void)HTTPOperationDidFinish:(NSNotification *)notification {
AFHTTPRequestOperation *operation = (AFHTTPRequestOperation *)[notification object];
if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
return;
}
if ([operation.response statusCode] == 401) {
// enqueue a new request operation here
}
}
編集:
一般に、これを行う必要はなく、このAFNetworkingメソッドで認証を処理するだけです。
- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
私はAFNetworking 2.0でこれを行うための代替手段を使用しています。
dataTaskWithRequest:success:failure:
と渡された完了ブロックをいくつかのエラーチェックでラップします。たとえば、OAuthを使用している場合は、401エラー(期限切れ)を監視し、アクセストークンを更新できます。
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)urlRequest completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))originalCompletionHandler{
//create a completion block that wraps the original
void (^authFailBlock)(NSURLResponse *response, id responseObject, NSError *error) = ^(NSURLResponse *response, id responseObject, NSError *error)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if([httpResponse statusCode] == 401){
NSLog(@"401 auth error!");
//since there was an error, call you refresh method and then redo the original task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//call your method for refreshing OAuth tokens. This is an example:
[self refreshAccessToken:^(id responseObject) {
NSLog(@"response was %@", responseObject);
//store your new token
//now, queue up and execute the original task
NSURLSessionDataTask *originalTask = [super dataTaskWithRequest:urlRequest completionHandler:originalCompletionHandler];
[originalTask resume];
}];
});
}else{
NSLog(@"no auth error");
originalCompletionHandler(response, responseObject, error);
}
};
NSURLSessionDataTask *task = [super dataTaskWithRequest:urlRequest completionHandler:authFailBlock];
return task;
}
これは、ユーザー@adamupのSwift実装です answer
class SessionManager:AFHTTPSessionManager{
static let sharedInstance = SessionManager()
override func dataTaskWithRequest(request: NSURLRequest!, completionHandler: ((NSURLResponse!, AnyObject!, NSError!) -> Void)!) -> NSURLSessionDataTask! {
var authFailBlock : (response:NSURLResponse!, responseObject:AnyObject!, error:NSError!) -> Void = {(response:NSURLResponse!, responseObject:AnyObject!, error:NSError!) -> Void in
var httpResponse = response as! NSHTTPURLResponse
if httpResponse.statusCode == 401 {
//println("auth failed")
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), { () -> Void in
self.refreshToken(){ token -> Void in
if let tkn = token{
var mutableRequest = request.mutableCopy() as! NSMutableURLRequest
mutableRequest.setValue(tkn, forHTTPHeaderField: "Authorization")
var newRequest = mutableRequest.copy() as! NSURLRequest
var originalTask = super.dataTaskWithRequest(newRequest, completionHandler: completionHandler)
originalTask.resume()
}else{
completionHandler(response,responseObject,error)
}
}
})
}
else{
//println("no auth error")
completionHandler(response,responseObject,error)
}
}
var task = super.dataTaskWithRequest(request, completionHandler:authFailBlock )
return task
}}
ここで、refreshToken(...)は、サーバーから新しいトークンを取得するために作成した拡張メソッドです。
同様のアプローチを取ったが、phix23の回答でステータスコードオブジェクトを取得できなかったため、別のアクションプランが必要でした。 AFNetworking 2.0はいくつかの点を変更しました。
-(void)networkRequestDidFinish: (NSNotification *) notification
{
NSError *error = [notification.userInfo objectForKey:AFNetworkingTaskDidCompleteErrorKey];
NSHTTPURLResponse *httpResponse = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey];
if (httpResponse.statusCode == 401){
NSLog(@"Error was 401");
}
}
AFHTTPSessionManager
をサブクラス化する場合、またはAFURLSessionManager
を直接使用する場合は、次のメソッドを使用して、タスクの完了後に実行されるブロックを設定できます。
/**
Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`.
@param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task.
*/
- (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block;
その中のセッションの各タスクに対して実行したいことを実行するだけです。
[self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse.statusCode == 500) {
}
}
}];
編集:実際、応答オブジェクトで返されたエラーを処理する必要がある場合、上記のメソッドは機能しません。 AFHTTPSessionManager
をサブクラス化する場合の1つの方法は、サブクラス化し、それを使用してカスタム応答シリアライザーを設定することですresponseObjectForResponse:data:error:
オーバーロード:
@interface MyJSONResponseSerializer : AFJSONResponseSerializer
@end
@implementation MyJSONResponseSerializer
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
id responseObject = [super responseObjectForResponse:response data:data error:error];
if ([responseObject isKindOfClass:[NSDictionary class]]
&& /* .. check for status or error fields .. */)
{
// Handle error globally here
}
return responseObject;
}
@end
AFHTTPSessionManager
サブクラスに設定します。
@interface MyAPIClient : AFHTTPSessionManager
+ (instancetype)sharedClient;
@end
@implementation MyAPIClient
+ (instancetype)sharedClient {
static MyAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[MyAPIClient alloc] initWithBaseURL:[NSURL URLWithString:MyAPIBaseURLString]];
_sharedClient.responseSerializer = [MyJSONResponseSerializer serializer];
});
return _sharedClient;
}
@end
複数のトークンの更新がほぼ同時に発行されないようにするには、トークンが更新されるときにネットワーク要求をキューに入れてキューをブロックするか、トークンの更新メソッドにミューテックスロック(@synchronizedディレクティブ)を追加することをお勧めします。