38

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

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

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

我认为这可以通过一些全局完成/错误处理程序来完成AFHTTPClient,但我没有发现任何有用的东西。那么,做我想做的事的“正确”方法是什么?enqueueHTTPRequestOperation:在我的子类中覆盖AFHTTPClient,复制操作并用我想要的块包装原始完成处理程序(重新验证,排队复制操作)?还是我完全走错了路?

谢谢!

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

4

6 回答 6

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;

}
于 2014-03-03T22:12:55.117 回答
21

在 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;
于 2012-10-15T15:20:28.170 回答
4

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

于 2015-08-12T19:00:46.573 回答
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");
    }
}
于 2014-06-12T20:41:39.257 回答
0

如果您是子类化AFHTTPSessionManager或直接使用 anAFURLSessionManager您可以使用以下方法设置任务完成后执行的块:

/**
 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
于 2015-06-04T21:27:31.687 回答
0

为确保不会同时发出多个令牌刷新,最好将网络请求排队并在令牌刷新时阻塞队列,或者在令牌刷新方法中添加互斥锁(@synchronized 指令)。

于 2016-01-13T21:33:43.490 回答