web-dev-qa-db-ja.com

AFNetworking:エラーをグローバルに処理し、リクエストを繰り返す

私はかなり一般的であるべきユースケースを持っていますが、AFNetworkingでそれを処理する簡単な方法を見つけることができません。

サーバーがanyリクエストに対して特定のステータスコードを返すときはいつでも、次のことを実行します。

  • キャッシュされた認証トークンを削除する
  • 再認証(これは別の要求です)
  • 失敗した要求を繰り返します。

これはAFHTTPClientのグローバルな完了/エラーハンドラーを介して実行できると思いましたが、何も役に立たなかったようです。それで、私が望むことをするための「正しい」方法は何ですか?上書きenqueueHTTPRequestOperation:AFHTTPClientサブクラスで、操作をコピーし、元の完了ハンドラーを、私がやりたいことを行うブロックでラップします(再認証、コピーされた操作のエンキュー)?それとも私は完全に間違ったトラックにいますか?

ありがとう!

編集:401ステータスコードへの参照を削除しました。これは、トークン認証を使用している間はおそらくHTTPベーシック用に予約されているためです。

37
Daniel Rinser

AFHTTPClientのinitメソッドで、AFNetworkingOperationDidFinishNotificationを登録します。これは、リクエストの完了後にポストされます。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(HTTPOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];

通知ハンドラで、ステータスコードとcopyAFHTTPRequestOperationを確認するか、新しいコードを作成します。

- (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;
21
Felix

私は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;

}
30
adamup

これは、ユーザー@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(...)は、サーバーから新しいトークンを取得するために作成した拡張メソッドです。

4
darthjit

同様のアプローチを取ったが、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");
    }
}
2
drees

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
0
Bluezen

複数のトークンの更新がほぼ同時に発行されないようにするには、トークンが更新されるときにネットワーク要求をキューに入れてキューをブロックするか、トークンの更新メソッドにミューテックスロック(@synchronizedディレクティブ)を追加することをお勧めします。

0
Ryan