Objective-C Method Swizzle在AFNet

作者: 穿越地平线的渴望__ | 来源:发表于2017-03-31 12:29 被阅读318次

    阐述#

    相信iOS开发的同学如果使用了AFNetworking这个第三方框架,可能会碰到以下Bug:

    Error Domain=com.alamofire.error.serialization.responseCode=-1016"Request failed:
     unacceptable content-type: text/html"UserInfo={com.alamofire.serialization.response.error.response
    = { URL: http://c.m.163.com/nc/article/headline/T1348647853363/0-140.html }
    { statuscode:200, headers { .....}......22222c22626f6172646964223a226e6577735f7368656
    87569375f626273222c227074696d65223a22323031362d30332d30332031313a30323a3435227d5d7d>,
     NSLocalizedDescription=Request failed: unacceptablecontent-type: text/html}
    
    

    众所周知,这是AFNetworking不支持解析text/html(非官方的html)格式的数据.

    思路#

    按道理讲:我们只要把

    self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",
    @"text/javascript",nil];
    
    变成
    self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];
    

    思路是完全正确的,但是我们真正处理起来就会有麻烦:

    我们一般是用cocoapods来管理第三方库的,方便第三方库的版本更新。既然是pod管理的,我们就无法去更改源代码,即便更改了,下次pod更新了,也会把修改好的代码覆盖掉。这样我们就需要其他方法来彻底解决这个问题了。

    @implementation  AFJSONResponseSerializer
    - (instancetype)init {
      self= [super init];
      if(!self) {
         return nil;
      }
      self.acceptableContentTypes= [NSSet setWithObjects:@"application/json",@"text/json",@"text/javascript",nil];
      return self;
    }
    

    上面这个方法是在AFJSONResponseSerializer 的init方法了设置acceptableContentTypes。如果我们能够复写AFJSONResponseSerializer的init方法或者替换掉这个方法,改成我们想实现的,应该就可以实现了!

    首先,大家想到的肯定是通过子类继承的方法去复写此方法,但是继承肯定不方便了。
    那么,通过类目去复写此方法,能不能实现呢?答案是否定的

    1.类目中不能super

    2.类目的本质是给原始类增加方法,而不是修改修改原始类的方法.要覆盖原始类的方法可以通过子类继承的方式,这里用继承肯定不方便了。

    重要的事情说三遍:
    不要类目在覆盖原始类的方法
    不要类目在覆盖原始类的方法
    不要类目在覆盖原始类的方法

    原因是:纵观苹果的api,以NSString为例,有NSStringExtensionMethods,NSStringEncodingDetection等各种分类方法,如果我们再写一个分类覆盖原分类里的方法,那么系统就无法区分是A分类方法覆盖B分类的方法,还是B分类方法覆盖A分类的方法。

    最后,只有最后一个办法了,通过类目去替换掉AFJSONResponseSerializer的init方法.
    原理就是Method Swizzle

    解决方案#

    代码实现如下:

    #import <AFNetworking/AFNetworking.h>
    @interface AFJSONResponseSerializer (Category)
    @end
    
    #import" AFJSONResponseSerializer+Category.h"
    @implementation AFJSONResponseSerializer (Category)
    
    + (void)load {
       staticdispatch_once_tonceToken;
       dispatch_once(&onceToken, ^{
       Method orignalMethod =class_getClassMethod([selfclass],@selector(init));            
       Method swizzledMethod   
         =class_getClassMethod([selfclass],@selector(dev4mobile_init));
       method_exchangeImplementations(orignalMethod, swizzledMethod);
      });
    }
    
    - (instancetype)dev4mobile_init {
        [self dev4mobile_init];
        self.acceptableContentTypes = [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];
        return self;
    }
    @end
    

    此处要注意的有几点:

    1.load是在程序的main函数之前就调用的,当程序开始运行,而不是编译的时候,调用load方法。未了保证全局的去交换AFJSONResponseSerializer的init方法和dev4mobile_init的IMP(函数指针)。

    2.用dispatch_once去保证两个方法的指针只交换一次。为了避免多线程出现多次调用的结果。

    3.有的人可能觉得调用[self dev4mobile_init];方法时会产生递归。其实不然,正确的顺序是这样的,AFJSONResponseSerializer先调用自身init方法,但是指向init方法的selector已经指向了dev4mobile_init方法了,所以会调到分类方法中,而调用[self dev4mobile_init];方法是,指向dev4mobile_init的selector指向的是AFJSONResponseSerializer的init方法,走完init方法,就会走

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

    这行代码了。

    4.Method swizzledMethod原理就是交换两个selector所指向的IMP.还有很多其他的实际用途:例如你不想调用UITextField一个的代理方法,想调到自定义的方法,此时就可以用她来hook一下。

    5.把dev4mobile_init换成xxx_init(xxx为自己命名的前缀)。

    这里是关于Method Swizzle的几个陷阱:
    Method swizzling is not atomic
    Changes behavior of un-owned
    codePossible naming conflicts
    Swizzling changes the method's arguments
    The order of swizzles matters
    Difficult to understand (looks recursive)
    Difficult to debug

    大家自己体会一下。


    推荐念茜的一篇Method Swizzle博客给大家http://blog.csdn.net/yiyaaixuexi/article/details/9374411

    相关文章

      网友评论

      本文标题:Objective-C Method Swizzle在AFNet

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