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

我有一个用例,应该是相当普遍的,但我找不到一个简单的方法来处理它AFNetworking:

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

  • 删除caching的身份validation令牌
  • 重新authentication(这是一个单独的请求)
  • 重复失败的请求。

我认为这可以通过AFHTTPClient一些全局完成/error handling程序完成,但是我没有find任何有用的东西。 那么,做我想做的事情的“正确”方法是什么? 覆盖enqueueHTTPRequestOperation:在我的AFHTTPClient子类中,复制操作,并包装原始完成处理程序与我想要的块(重新authentication,入队复制操作)? 还是我在错误的轨道上?

谢谢!

编辑:删除对401状态码的引用,因为这可能保留为HTTP基本,而我使用令牌authentication。

在AFHTTPClient的init方法注册AFNetworkingOperationDidFinishNotification将在请求完成后发布。

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

在通知处理程序中检查状态码并copy AFHTTPRequestOperation或创build一个新的。

 - (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方法处理身份validation:

 - (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; } 

采取了类似的方法,但我不能用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"); } } 

这是用户@adamup 答案的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(…)是我写的从服务器获取新令牌的扩展方法。

如果您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 

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