美文网首页Ios开发iOSnetwork
使用AFNetworking框架遇到的一个经典bug的解决方案

使用AFNetworking框架遇到的一个经典bug的解决方案

作者: DXSmile | 来源:发表于2016-03-04 00:00 被阅读9980次

    以上为个人愚见, 如有不妥,望大家斧正!!!
    本文的GitHub源码下载地址:
    https://github.com/DXSmile/Bug---AFNetworking.git

    如需转载,请注明转载自DXSmile
    GitHub项目https://github.com/DXSmile/Bug---AFNetworking.git

    阐述:

    在获取网络数据的时候, 我们一般会使用到一个非常著名的框架: AFNetworking框架, 可以说,这是作为iOS攻城狮必备的框架之一;
    这个框架是非常强大的一个框架, 对于发送异步请求来说,简直没有比这个更好用的了, 不过,在使用的过程中,我们可能会遇到这样一个bug: 如下

    连接出错 Error Domain=com.alamofire.error.serialization.response Code=-1016 
    "Request failed: unacceptable content-type: text/html" UserInfo=
    {com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x7f93fad1c4b0> 
    { URL: http://c.m.163.com/nc/article/headline/T1348647853363/0-140.html } 
    { status code: 200, headers { .....}
    ...... 
     22222c22 626f6172 64696422 3a226e65 77735f73 68656875 69375f62 6273222c 22707469 6d65223a 22323031 362d3033 2d303320 31313a30 323a3435 227d5d7d>,
     NSLocalizedDescription=Request failed: unacceptable content-type: text/html}
    

    说明:

    由于数据很多,所以返回的请求体,和响应体部分我用省略号(......)代替了, 但是,通过上面的返回的信息,我们不难看出,状态码是200, 而且也有一堆数据, 但是在tableviewCell中就是没有显示, 在最后的时候还出现"NSLocalizedDescription=Request failed: unacceptable content-type: text/html} " 这样一句话;

    分析:那么这个错误是什么原因造成的呢?

    通过这句话:unacceptable content-type: text/html,我们可以看出报错原因:是不接收的内容类型,也就是说AFNetworking框架不支持解析text/html这种格式. 那么怎样解决呢?

    首先我们需要明白: AFNetworking为什么能够解析服务器返回的东西呢?

    因为manager有一个responseSerializer属性.它只设置了一些固定的解析格式.其中不包含text/html这种数据的格式.因为解析报错了.
    我们来看一下AFNetworking解析格式的底层:

     self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
    

    通过底层,我们也可以看见,确实是没有text/html这种数据的格式的,

    那如何解决这个问题呢?

    错误的解决方法
    下面我尝试了三种方法:

    解决方法1: 直接给acceptableContentTypes属性添加类型

    解决之前:

     self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
    
    解决之前

    着手解决:

       AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManagermanager];
    
        // 添加 text/html 类型到可接收内容类型中
        mgr.responseSerializer.acceptableContentTypes= [NSSetsetWithObjects:@"text/html", nil];
    

    运行结果:

    解决之后:

    通过执行结果可以很明显的看得出,我们已经非常成功的获取到了数据;

    对方法1的思考:

    **首先,我们可以明显的看出,方法1确实是可以解决问题的,但是这样解决真的好吗? 不一定!
    为什么呢? 很简单, 如果我们只是发送一条网络请求,无疑方法1是最恰当的解决方案了, 但是实际开发中,我们不可能只发一次请求, 那么就需要我们每次发请求的时候都来写一次这些代码; 当然,如果您愿意写,那我也没办法多说什么了;
    很显然,这个方法是存在不足的! **

    于是我们有了第二种方法:

    解决办法2: 直接到框架的源代码中添加类型

    解决之前:

     self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
    

    解决之后:

     self.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json" ,@"text/javascript", nil];
    
    分析方法2:

    不得不说,这也是一种办法, 而且釜底抽薪,效果方面呢,当然也是显而易见了, 但是, 注意了,这个方法2 又真的恰当吗? 真的好吗?

    我们来假设一种情况, 而且实际开发中必然会发生的情况: 这个框架更新了!!!
    对,就是更新了!!! 更新了显然又会回到之前的状态
    傻眼了吧? **
    实际开发中,我们都会用cocoaPods来管理我们的第三方框架, 当某个框架更新之后, cocoaPods会下载最新的框架源码镶嵌到我们的项目中, 我们并不能保证AFNetworking这个框架一定会把我们需要的类型添加上去, 所以
    每一次更新,我们都需要针对源码再做一次修改**
    很显然,这也是费力不讨好的;

    那么有没有一劳永逸的方法呢? 别急,马上就来!!!

    解决办法3: 自定义一个manager ,拓展一个类型

    >>这里需要考虑到两种情况: 如下
    >1. 如果你的APP只需要适配iOS7.0之前的版本,为了能够适配旧系统,需要使用 AFHTTPRequestOperationManager
    >2. 如果你的APP只需要适配iOS7.0之后的版本,那么你需要 自定义的类是继承AFHTTPSessionManager的

    这里我只简单的介绍iOS7.0之后的版本,

    1> 自定义manager,继承自AFHTTPSessionManager

    @interface DXHTTPManager : AFHTTPSessionManager
    

    2> 在.m文件中,重写父类的manager方法 目的 : 添加类型

    + (instancetype)manager {
        DXHTTPManager *mgr = [super manager];
        // 创建NSMutableSet对象
        NSMutableSet *newSet = [NSMutableSet set];
        // 添加我们需要的类型 
        newSet.set = mgr.responseSerializer.acceptableContentTypes;
        [newSet addObject:@"text/html"];
        
        // 重写给 acceptableContentTypes赋值
        mgr.responseSerializer.acceptableContentTypes = newSet;
        
        return mgr;
    }
    

    3> 在发送请求的时候,使用我们自定义的类来发送请求

    [[DXHTTPManager manager] GET:@"http://...." 
    parameters:nil success:^(AFHTTPRequestOperation *operation, NSDictionary * responseObject) 
    {
        NSLog(@"请求成功 -- %@",responseObject);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"请求失败 -- %@",error);
    }];
    

    执行结果:

    方法3执行结果:
    结论:

    **1. 使用方法3来解决这个bug,虽然看似比第一种,第二种要繁琐一些,实则可拓展性,和维护方面,要好得多,以后我们开发项目的时候, 只需要将我们自定义的这个类拖进去就可以了, 假如有需要新的类型的时候, 也只是简单的多配置一下类型即可, **
    2.此外方法3还有一个好处,我们假设一个情景: 如果有一天我们使用的这个AFNetworking框架不再更新了,甚至作废了呢? 这种可能性还是有的,如果我们用方法1和方法2的话, 那个时候你会在项目中看见一堆一堆的bug,
    而如果我们使用的方法3的话,那么只需要简单的将继承的类更换成我们最新使用的框架里面的类,然后做一些简单的配置即可,这样维护起来的成本自然就轻松了,

    3.而这也正是我们代码重构, 和优化项目架构的思路之一!!!

    相关文章

      网友评论

      • 天堂秀:笔者我也遇到了一样的问题 想问你第三种方法还是没能解决我的问题Request failed: bad request (400)" UserInfo={com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x60800022d420> { URL: https://api.weibo.com/oauth2/access_token 具体报错原因是这样
        cell波:我用同样的方法 报了3840错误
        天堂秀:@DXSmile 是的
        DXSmile:@天堂秀 这是报的服务器问题
      • 穿越地平线的渴望__:你好,我写了一遍文章用MethodSwizzled解决的的.http://www.jianshu.com/p/3b84c0fa010a
      • Find_YAM:如果是text/html,但是数据不是 json 格式会不会造成其他问题呢?
      • 秋燕归:额。不是单例,我说错了。
      • 秋燕归:了解后台的ios开发会和后台说:请返回application/json。

        另外afnetworking的manager是一个单例,在appdelgate里面拿到并添加html/text即可。

        当然了,楼主的观点是好的,以上个人拙见,不喜勿喷
        65067d1326a2:@DXSmile 是啊,我说的就不是单例啊。。。不知道怎么删了。。我看manger的源代码没有单例的代码
        DXSmile:@醋溜草莓便当 manger创建的并不是单例, 这个可以验证的,比如多创建几个manger,然后输出它们的内存地址,就可以看到,彼此的内存地址都是不一样的, 所以manger创建的并不是单例;
        另外,我们也可以从另一个角度来思考: manger是用来发送网络请求的, 而一个项目中,发送的请求是很多的, 不可能都是一样的请求, 如果是单例,显然是不合理的;
        DXSmile:@柠檬1989 你的观点也是对的 ,操作起来同样是可行的, 我这里做一个分类呢,是为了便于后期的维护, 将可能出现的变化和不稳定因素给隔离开, 以后不管这个框架怎样变化, 我们只需要简单的修改一下分类里面的数据就可以了,
        不过呢, 解决问题的方法千百种, 只要能解决问题就是好方法 :blush:

      本文标题:使用AFNetworking框架遇到的一个经典bug的解决方案

      本文链接:https://www.haomeiwen.com/subject/uakokttx.html