J'ai un cas d'utilisation qui devrait être assez courant mais je ne trouve pas de moyen facile de le gérer avec AFNetworking:
Chaque fois que le serveur renvoie un code d'état spécifique pour any request, je souhaite:
Je pensais que cela pourrait être fait via un gestionnaire d'achèvement/d'erreur global dans AFHTTPClient
, mais je n'ai rien trouvé d'utile. Alors, quelle est la "bonne" façon de faire ce que je veux? Remplacez enqueueHTTPRequestOperation:
dans ma sous-classe AFHTTPClient
, copiez l'opération et encapsulez le gestionnaire d'achèvement d'origine avec un bloc qui fait ce que je veux (réauthentifier, mettre en file d'attente l'opération copiée)? Ou suis-je sur la mauvaise voie?
Merci!
EDIT: Suppression de la référence au code d'état 401, car elle est probablement réservée à HTTP basic lorsque j'utilise l'authentification par jeton.
Dans la méthode init de AFHTTPClient, enregistrez pour la AFNetworkingOperationDidFinishNotification
qui sera publiée à la fin de la demande.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(HTTPOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];
Dans le gestionnaire de notifications, vérifiez le code d'état et copy
la AFHTTPRequestOperation
ou créez-en un nouveau.
- (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
}
}
MODIFIER:
En général, vous ne devriez pas avoir à faire cela et simplement gérer l'authentification avec cette méthode AFNetworking:
- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
J'utilise un autre moyen de le faire avec AFNetworking 2.0.
Vous pouvez sous-classer dataTaskWithRequest:success:failure:
et envelopper le bloc d'achèvement passé avec une vérification d'erreur. Par exemple, si vous travaillez avec OAuth, vous pouvez rechercher une erreur 401 (expiration) et actualiser votre jeton d'accès.
- (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;
}
Voici l'implémentation Swift de l'utilisateur @adamup ' answer
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
}}
où refreshToken (...) est une méthode d'extension que j'ai écrite pour obtenir un nouveau jeton du serveur.
J'ai adopté une approche similaire, mais je ne pouvais pas obtenir l'objet code d'état avec la réponse de phix23, donc j'avais besoin d'un plan d'action différent. AFNetworking 2.0 a changé plusieurs choses.
-(void)networkRequestDidFinish: (NSNotification *) notification
{
NSError *error = [notification.userInfo objectForKey:AFNetworkingTaskDidCompleteErrorKey];
NSHTTPURLResponse *httpResponse = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey];
if (httpResponse.statusCode == 401){
NSLog(@"Error was 401");
}
}
Si vous sous-classez AFHTTPSessionManager
ou utilisez directement un AFURLSessionManager
, vous pouvez utiliser la méthode suivante pour définir un bloc exécuté après l'achèvement d'une tâche:
/**
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;
Effectuez simplement ce que vous voulez faire pour chacune des tâches de la session:
[self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse.statusCode == 500) {
}
}
}];
EDIT: En fait, si vous devez traiter une erreur renvoyée dans l'objet de réponse, la méthode ci-dessus ne fera pas le travail . Une façon de sous-classer AFHTTPSessionManager
pourrait être de sous-classe et définissez un sérialiseur de réponse personnalisé avec responseObjectForResponse:data:error:
surchargé de la manière suivante:
@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
et définissez-le dans votre AFHTTPSessionManager
sous-classe:
@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
Pour vous assurer que plusieurs actualisations de jetons ne sont pas émises à peu près au même moment, il est utile de mettre vos demandes réseau en file d'attente et de la bloquer lorsque le jeton est actualisé ou d'ajouter un verrou mutex (directive @synchronized) à votre méthode d'actualisation de jetons.