objective-c - afnetworking example
AFNetworking 2.0 AFHTTPSessionManager: cómo obtener el código de estado y la respuesta JSON en el bloqueo de fallas? (7)
Cuando se cambia a AFNetworking 2.0, el AFHTTPClient ha sido reemplazado por AFHTTPRequestOperationManager / AFHTTPSessionManager (como se menciona en la guía de migración). El primer problema que encontré cuando uso el AFHTTPSessionManager es cómo recuperar el cuerpo de la respuesta en el bloque de falla.
Aquí hay un ejemplo:
[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?
}];
En el bloque de éxito me gustaría recuperar el código de estado de la respuesta. En el bloque de falla me gustaría recuperar el código de estado de la respuesta y el contenido (que es JSON en este caso que describe el error del lado del servidor).
NSURLSessionDataTask tiene una propiedad de respuesta de tipo NSURLResponse, que no tiene el campo statusCode. Actualmente puedo recuperar statusCode así:
[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);
}];
Pero esto se ve feo para mí. Y todavía no puedo entender el cuerpo de la respuesta.
¿Alguna sugerencia?
Después de leer e investigar durante varios días, funcionó para mí:
1) Tienes que construir tu propia subclase de AFJSONResponseSerializer
Archivo: JSONResponseSerializerWithData.h:
#import "AFURLResponseSerialization.h"
/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = @"JSONResponseSerializerWithDataKey";
@interface JSONResponseSerializerWithData : AFJSONResponseSerializer
@end
Archivo: 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) Configure su propio JSONResponseSerializer en su 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);
}
En Swift 2.0 (en caso de que aún no pueda usar Alamofire):
Obtener código de estado:
if let response = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey] as? NSHTTPURLResponse {
print(response.statusCode)
}
Obtenga datos de respuesta:
if let data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
print("/(data.length)")
}
Algunas API REST de JSON devuelven mensajes de error en sus respuestas de error (servicios Amazon AWS, por ejemplo). Uso esta función para extraer el mensaje de error de un NSError que ha sido lanzado por 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
}
}
Hay otro enfoque además de la respuesta aceptada.
AFNetworking está llamando a su bloque de falla, sin ningún objeto de respuesta, porque cree que se ha producido un verdadero error (por ejemplo, una respuesta HTTP 404, tal vez). La razón por la que interpreta 404 como un error es porque 404 no está en el conjunto de "códigos de estado aceptables" propiedad del serializador de respuestas (el rango predeterminado de códigos aceptables es 200-299). Si agrega 404 (o 400, o 500, o lo que sea) a ese conjunto, entonces una respuesta con ese código se considerará aceptable y se enrutará a su bloque de éxito en su lugar, complete con el objeto de respuesta decodificado .
¡Pero 404 es un error! ¡Quiero que mi bloque de fallas reciba errores! Si ese es el caso, utilice la solución a la que se refiere la respuesta aceptada: https://github.com/AFNetworking/AFNetworking/issues/1397 . Pero considere que tal vez un 404 sea realmente un éxito si va a extraer y procesar el contenido. En este caso, su bloque de falla maneja fallas reales, por ejemplo, dominios no resueltas, tiempos de espera de red, etc. Puede recuperar fácilmente el código de estado en su bloque de éxito y procesarlo en consecuencia.
Ahora entiendo, podría ser súper agradable si AFNetworking pasara cualquier responseObject al bloque de falla. Pero no es así
_sm = [[AFHTTPSessionManager alloc] initWithBaseURL: [NSURL URLWithString: @"http://www..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 );
}];
Puede acceder al objeto "datos" directamente desde AFNetworking utilizando la clave "AFNetworkingOperationFailingURLResponseDataErrorKey", por lo que no es necesario crear subclases de AFJSONResponseSerializer. Puede serializar los datos en un diccionario legible. Aquí hay un código de muestra para obtener datos JSON:
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Aquí está el código para obtener código de estado en el bloque de falla:
NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
NSLog( @"success: %d", r.statusCode );
Puede acceder al objeto "datos" directamente desde AFNetworking utilizando la clave "AFNetworkingOperationFailingURLResponseDataErrorKey", por lo que no es necesario crear subclases de AFJSONResponseSerializer. Puede serializar los datos en un diccionario legible. Aquí hay un código de muestra:
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];
Puede obtener el diccionario userInfo
asociado con el objeto NSError
y recorrerlo para obtener la respuesta exacta que necesita. Por ejemplo, en mi caso recibo un error de un servidor y puedo ver el userInfo
como en ScreeShot
Puedes obtener el código de estado así, leer el bloque de falla ...
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;}