【问题标题】:AFNetworking 2.0 AFHTTPSessionManager: how to get status code and response JSON in failure block?AFNetworking 2.0 AFHTTPSessionManager:如何在失败块中获取状态码和响应 JSON?
【发布时间】:2013-10-06 10:54:03
【问题描述】:

当切换到 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);

}];

但这在我看来很难看。仍然无法弄清楚响应的正文。

有什么建议吗?

【问题讨论】:

  • 这基本上就是你这样做的方式,没有更清洁的方法不下降到[AFURLSessionManager dataTaskWithRequest:completionHandler:],它会通过相同的NSURLResponse(你也必须在那里投射)
  • 回答了如何获取 statusCode。不幸的是 NSHTTPURLResponse 不包含失败块中的正文/数据:(
  • 如果您观察任务的 AFNetworkingTaskDidFinishNotification 通知,则该响应似乎在通知 userInfo 键 AFNetworkingTaskDidFinishResponseDataKey 中可用。如果这有帮助,我会把它写下来作为下面的答案。
  • 如果这是唯一可用的解决方案,除了子类化管理器,那为什么不……伙计,完美工作的 AFHTTPClient 因概念上的怪异而被废弃!
  • 您可以在 GitHub 上提出问题并要求将响应数据传递到错误的 userInfo 字典中。肯定会更干净!

标签: objective-c afnetworking-2


【解决方案1】:

您可以使用“AFNetworkingOperationFailingURLResponseDataErrorKey”键直接从 AFNetworking 访问“数据”对象,因此无需对 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 ); 

【讨论】:

  • 感谢指点!然而,应该注意的是,这是一个相对较新的添加(AFNetworking 2.4.0,于 2014 年 9 月 3 日发布),因此在提出问题时不可用。 @Olx:这现在应该是公认的答案!
【解决方案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) 在 AFHTTPSessionManager 中设置自己的 JSONResponseSerializer

+ (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】:

      您可以使用“AFNetworkingOperationFailingURLResponseDataErrorKey”键直接从 AFNetworking 访问“数据”对象,因此无需对 AFJSONResponseSerializer 进行子类化。您可以将数据序列化为可读的字典。 这是一些示例代码:

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

      【讨论】:

      • 您可能应该删除这个重复的答案...在您的答案中添加内容时,请编辑现有的而不是创建新的。
      • 这个答案很好,如果您需要响应字符串,您可以使用NSString *errString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];
      【解决方案5】:

      除了接受的答案之外,还有另一种方法。

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

      【讨论】:

      • 正是我需要的,很好。
      【解决方案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)")
      }
      

      一些 JSON REST API 在其错误响应中返回错误消息(例如 Amazon AWS 服务)。我使用这个函数从 AFNetworking 抛出的 NSError 中提取错误消息:

      // 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】:

        您可以获得与NSError 对象关联的userInfo 字典并向下遍历以获得您需要的确切响应。例如,在我的情况下,我从服务器收到错误,我可以看到 userInfo,就像在 ScreeShot 中一样

        【讨论】:

        • 在我的情况下,它没有提供如此详细的用户信息,如下面的屏幕截图所示。 Example Screenshot.
        • 这意味着服务器没有在其失败响应正文中添加所需的内容/数据。我建议您让服务器端开发人员在其响应正文中为您提供所需的数据
        • 服务器返回正确的响应正文。我可以在 AFNetworkingTaskDidFinishNotification 中看到它(出于调试原因)。
        • unspokenblabber 请发布您的代码。因为我的屏幕截图和@Olx 一样
        • 服务器响应 RoR 中的以下代码。 ==> render status:400, json:{message:"Please enter the user email and password."} return
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-01-01
        • 2016-02-19
        • 1970-01-01
        • 1970-01-01
        • 2018-10-15
        • 2013-12-23
        • 2014-09-25
        相关资源
        最近更新 更多