AFNetworking:全局处理错误并重复请求

时间:2012-10-15 10:37:22

标签: ios networking afnetworking

我有一个应该相当常见的用例,但我找不到使用AFNetworking处理它的简单方法:

每当服务器返回任何请求的特定状态代码时,我想:

  • 删除缓存的身份验证令牌
  • 重新验证(这是一个单独的请求)
  • 重复失败的请求。

我认为这可以通过AFHTTPClient中的一些全局完成/错误处理程序来完成,但我找不到任何有用的东西。那么,做“我想要的”的“正确”方式是什么?在我的enqueueHTTPRequestOperation:子类中重写AFHTTPClient,复制操作并用一个执行我想要的块(重新验证,排队复制操作)包装原始完成处理程序?或者我完全走错了轨道?

谢谢!

编辑:删除了对401状态代码的引用,因为在我使用令牌身份验证时,这可能是为HTTP basic保留的。

6 个答案:

答案 0 :(得分:30)

我使用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;

}

答案 1 :(得分:21)

在AFHTTPClient的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;

答案 2 :(得分:4)

以下是用户@adamup的answer

Swift 实现
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(...)是我为了从服务器获取新令牌而编写的扩展方法。

答案 3 :(得分:2)

采取了类似的方法,但我无法通过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");
    }
}

答案 4 :(得分:0)

如果您是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的子类,一种方法可能是子类化并设置一个自定义响应序列化器,其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

答案 5 :(得分:0)

要确保几乎不会在同一时间发出多个令牌刷新,在令牌刷新时排队网络请求并阻止队列,或者向令牌添加互斥锁(@synchronized指令)是有益的。刷新方法。