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指令)是有益的。
- 是否有可能使用自动布局获得dynamic表视图节标题高度?
- 无法提交iOS应用以供审核
- 无法同时满足约束,将尝试通过打破约束来恢复
- 初始编码器aDecoder究竟是什么?
- 更改不同设备方向上的UICollectionViewCell大小
- 错误ITMS-90032:“无效的图像path – 没有find在关键'CFBundleIcons'下引用的path的图像:AppIcon40x40”
- 更新到Xcode 8后的错误:“没有这样的模块”和“目标覆盖EMBEDDED_CONTENT_CONTAINS_SWIFT”build立设置“
- 如何自动调整UIScrollView的大小来适应内容
- 在UIImage和Base64string之间转换