我有一个应该相当常见的用例,但我找不到使用AFNetworking处理它的简单方法:
每当服务器返回任何请求的特定状态代码时,我想:
我认为这可以通过AFHTTPClient
中的一些全局完成/错误处理程序来完成,但我找不到任何有用的东西。那么,做“我想要的”的“正确”方式是什么?在我的enqueueHTTPRequestOperation:
子类中重写AFHTTPClient
,复制操作并用一个执行我想要的块(重新验证,排队复制操作)包装原始完成处理程序?或者我完全走错了轨道?
谢谢!
编辑:删除了对401状态代码的引用,因为在我使用令牌身份验证时,这可能是为HTTP basic保留的。
答案 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指令)是有益的。刷新方法。