Подтвердить что ты не робот

AFNetworking 2.0 AFHTTPSessionManager: как получить код состояния и ответ JSON в блоке отказов?

При переключении на AFNetworking 2.0 AFHTTPClient был заменен на AFHTTPRequestOperationManager/AFHTTPSessionManager (как указано в руководстве по миграции). Самая первая проблема, с которой я столкнулся при использовании AFHTTPSessionManager, заключается в том, как получить тело ответа в блоке отказов?

Вот пример:

[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?
}];

В блоке успеха я хотел бы получить код состояния ответа. В блоке отказа я хотел бы получить как код состояния ответа, так и контент (который JSON в этом случае описывает ошибку на стороне сервера).

У NSURLSessionDataTask есть свойство ответа типа NSURLResponse, которое не имеет поля statusCode. В настоящее время я могу получить statusCode следующим образом:

[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);

}];

Но это выглядит уродливо для меня. И все еще не может понять тело ответа.

Любые предложения?

4b9b3361

Ответ 1

Вы можете получить доступ к объекту "данные" непосредственно из AFNetworking с помощью ключа "AFNetworkingOperationFailingURLResponseDataErrorKey", поэтому нет необходимости подклассифицировать AFJSONResponseSerializer. Вы можете сериализовать данные в читаемый словарь. Вот пример кода для получения данных JSON:

 NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
 NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];

Вот код для получения кода состояния в блоке Failure:

  NSHTTPURLResponse* r = (NSHTTPURLResponse*)task.response;
  NSLog( @"success: %d", r.statusCode ); 

Ответ 2

После чтения и исследования в течение нескольких дней он работал у меня:

1) Вы должны создать свой собственный подкласс AFJSONResponseSerializer

Файл: JSONResponseSerializerWithData.h:

#import "AFURLResponseSerialization.h"

/// NSError userInfo key that will contain response data
static NSString * const JSONResponseSerializerWithDataKey = @"JSONResponseSerializerWithDataKey";

@interface JSONResponseSerializerWithData : AFJSONResponseSerializer
@end

Файл: 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) Настройте свой собственный JSONResponseSerializer в вашем 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);
}

Источник: http://blog.gregfiumara.com/archives/239

Ответ 3

Вы можете получить код состояния следующим образом, прочитать блок сбоев...

 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;}

Ответ 4

Существует другой подход, помимо принятого ответа.

AFNetworking вызывает ваш блок отказа, без какого-либо объекта ответа, потому что он считает, что произошел настоящий сбой (например, ответ HTTP 404, возможно). Причина, по которой он интерпретирует 404 как ошибку, состоит в том, что 404 не входит в набор "приемлемых кодов состояния", принадлежащих сериализатору ответов (диапазон допустимых кодов по умолчанию составляет 200-299). Если вы добавите 404 (или 400, или 500 или что-то еще) к этому набору, тогда ответ с этим кодом будет считаться приемлемым и вместо этого будет направлен на ваш блок успеха - в комплекте с объектом декодированного ответа.

Но 404 - это ошибка! Я хочу, чтобы мой блок отказов вызывался за ошибки! Если это случай, то используйте решение, на которое ссылается принятый ответ: https://github.com/AFNetworking/AFNetworking/issues/1397. Но подумайте, что, возможно, 404 действительно успех, если вы собираетесь извлекать и обрабатывать контент. В этом случае ваш блок отказов обрабатывает реальные сбои - например. неразрешимые домены, сетевые тайм-ауты и т.д. Вы можете легко получить код состояния в своем блоке успеха и обработать соответствующим образом.

Теперь я понимаю - это может быть очень хорошо, если AFNetworking передало любой объект responseObject в блок сбоя. Но это не так.

    _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 );


     }];

Ответ 5

Вы можете получить доступ к объекту "данные" непосредственно из AFNetworking с помощью ключа "AFNetworkingOperationFailingURLResponseDataErrorKey", поэтому нет необходимости подклассифицировать AFJSONResponseSerializer. Вы можете сериализовать данные в читаемый словарь. Вот пример кода:

 NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
 NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil];

Ответ 6

В Swift 2.0 (если вы еще не можете использовать Alamofire):

Получить код состояния:

if let response = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey] as? NSHTTPURLResponse {
    print(response.statusCode)
}

Получить данные ответа:

if let data = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData {
    print("\(data.length)")
}

Некоторые API-интерфейсы JSON REST возвращают сообщения об ошибках в сообщениях об ошибках (например, службы Amazon AWS). Я использую эту функцию для извлечения сообщения об ошибке из NSError, которое было выбрано 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
    }
}

Ответ 7

Вы можете получить словарь userInfo, связанный с объектом NSError, и повернуть его вниз, чтобы получить точный ответ, который вам нужен. Например, в моем случае я получаю сообщение об ошибке с сервера, и я вижу userInfo как в ScreeShot

ScreenShot displaying AFNetworking error