Lorsqu'il est passé à AFNetworking 2.0, AFHTTPClient a été remplacé par AFHTTPRequestOperationManager/AFHTTPSessionManager (comme mentionné dans le guide de migration). Le tout premier problème que j'ai rencontré lors de l'utilisation de AFHTTPSessionManager est de savoir comment récupérer le corps de la réponse dans le bloc d'échec?
Voici un exemple:
[self.sessionManager POST:[endpoint absoluteString] parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
// How to get the status code?
} failure:^(NSURLSessionDataTask *task, NSError *error) {
// How to get the status code? response?
}];
Dans le bloc de réussite, je voudrais récupérer le code d'état de la réponse. Dans le bloc d'échec, je voudrais récupérer à la fois le code d'état de la réponse et le contenu (qui est JSON dans ce cas qui décrit l'erreur côté serveur).
NSURLSessionDataTask a une propriété de réponse de type NSURLResponse, qui n'a pas de champ statusCode. Actuellement, je peux récupérer statusCode comme ceci:
[self.sessionManager POST:[endpoint absoluteString] parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
// How to get the status code?
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
DDLogError(@"Response statusCode: %i", response.statusCode);
}];
Mais cela me semble moche. Et je n'arrive toujours pas à comprendre le corps de la réponse.
Aucune suggestion?
Vous pouvez accéder à l'objet "data" directement depuis AFNetworking en utilisant la clé "AFNetworkingOperationFailingURLResponseDataErrorKey" de sorte qu'il n'est pas nécessaire de sous-classer AFJSONResponseSerializer. Vous pouvez sérialiser les données dans un dictionnaire lisible. Voici quelques exemples de code pour obtenir des données JSON:
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Voici le code pour obtenir le code d'état dans le bloc d'échec:
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( @"success: %d", r.statusCode );
Après plusieurs jours de lecture et de recherche, cela a fonctionné pour moi:
1) Vous devez créer votre propre sous-classe d'AFJSONResponseSerializer
Fichier: JSONResponseSerializerWithData.h:
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = @"JSONResponseSerializerWithDataKey";
@interface JSONResponseSerializerWithData : AFJSONResponseSerializer
@end
Fichier: JSONResponseSerializerWithData.m
#import "JSONResponseSerializerWithData.h"
@implementation JSONResponseSerializerWithData
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
id JSONObject = [super responseObjectForResponse:response data:data error:error];
if (*error != nil) {
NSMutableDictionary *userInfo = [(*error).userInfo mutableCopy];
if (data == nil) {
// // NOTE: You might want to convert data to a string here too, up to you.
// userInfo[JSONResponseSerializerWithDataKey] = @"";
userInfo[JSONResponseSerializerWithDataKey] = [NSData data];
} else {
// // NOTE: You might want to convert data to a string here too, up to you.
// userInfo[JSONResponseSerializerWithDataKey] = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
userInfo[JSONResponseSerializerWithDataKey] = data;
}
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:userInfo];
(*error) = newError;
}
return (JSONObject);
}
2) Configurez votre propre JSONResponseSerializer dans votre AFHTTPSessionManager
+ (instancetype)sharedManager
{
static CustomSharedManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[CustomSharedManager alloc] initWithBaseURL:<# your base URL #>];
// *** Use our custom response serializer ***
manager.responseSerializer = [JSONResponseSerializerWithData serializer];
});
return (manager);
}
Vous pouvez obtenir le code d'état comme celui-ci, lire le bloc d'échec ...
NSURLSessionDataTask *op = [[IAClient sharedClient] POST:path parameters:paramsDict constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
} success:^(NSURLSessionDataTask *task, id responseObject) {
DLog(@"\n============= Entity Saved Success ===\n%@",responseObject);
completionBlock(responseObject, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DLog(@"\n============== ERROR ====\n%@",error.userInfo);
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statuscode = response.statusCode;}
Vous pouvez accéder à l'objet "data" directement depuis AFNetworking en utilisant la clé "AFNetworkingOperationFailingURLResponseDataErrorKey" de sorte qu'il n'est pas nécessaire de sous-classer AFJSONResponseSerializer. Vous pouvez sérialiser les données dans un dictionnaire lisible. Voici un exemple de code:
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Il existe une autre approche que la réponse acceptée.
AFNetworking appelle votre bloc de défaillance, sans aucun objet de réponse, car il pense qu'une véritable défaillance s'est produite (par exemple, une réponse HTTP 404, peut-être). La raison pour laquelle il interprète le 404 comme une erreur est que le 404 ne fait pas partie de l'ensemble des "codes d'état acceptables" appartenant au sérialiseur de réponse (la plage par défaut des codes acceptables est 200-299). Si vous ajoutez 404 (ou 400, ou 500, ou autre) à cet ensemble, une réponse avec ce code sera considérée comme acceptable et sera acheminée vers votre bloc de réussite à la place - avec le décodé objet de réponse .
Mais 404 est une erreur! Je veux que mon bloc d'échec soit appelé pour des erreurs! Si tel est le cas, utilisez la solution mentionnée par la réponse acceptée: https://github.com/AFNetworking/AFNetworking/issues/1397 . Mais considérez que peut-être un 404 est vraiment un succès si vous allez extraire et traiter le contenu. Dans ce cas, votre bloc d'échec gère les échecs réels - par exemple domaines non résolubles, délais d'expiration du réseau, etc. Vous pouvez facilement récupérer le code d'état dans votre bloc de réussite et le traiter en conséquence.
Maintenant, je comprends - il pourrait être super sympa si AFNetworking transmettait un objet responseObject au bloc de défaillance. Mais ce n'est pas le cas.
_sm = [[AFHTTPSessionManager alloc] initWithBaseURL: [NSURL URLWithString: @"http://www.stackoverflow.com" ]];
_sm.responseSerializer = [AFHTTPResponseSerializer new];
_sm.responseSerializer.acceptableContentTypes = nil;
NSMutableIndexSet* codes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(200, 100)];
[codes addIndex: 404];
_sm.responseSerializer.acceptableStatusCodes = codes;
[_sm GET: @"doesnt_exist"
parameters: nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( @"success: %d", r.statusCode );
NSString* s = [[NSString alloc] initWithData: responseObject encoding:NSUTF8StringEncoding];
NSLog( @"%@", s );
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog( @"fail: %@", error );
}];
Dans Swift 2.0 (au cas où vous ne pourriez pas encore utiliser Alamofire):
Obtenez le code d'état:
if let response = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey] as? NSHTTPURLResponse {
print(response.statusCode)
}
Obtenez les données de réponse:
if let data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
print("\(data.length)")
}
Certaines API JSON REST renvoient des messages d'erreur dans leurs réponses d'erreur (services Amazon AWS par exemple). J'utilise cette fonction pour extraire le message d'erreur d'une erreur NSError lancée par AFNetworking:
// Example: Returns string "error123" for JSON { message: "error123" }
func responseMessageFromError(error: NSError) -> String? {
do {
guard let data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData else {
return nil
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String: String] else {
return nil
}
if let message = json["message"] {
return message
}
return nil
} catch {
return nil
}
}
Vous pouvez obtenir le dictionnaire userInfo
associé à l'objet NSError
et le parcourir pour obtenir la réponse exacte dont vous avez besoin. Par exemple, dans mon cas, je reçois une erreur d'un serveur et je peux voir le userInfo
comme dans ScreeShot