Objective-C Method Swizzle在AFNetworking中的实践

阐述#

相信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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,709评论 0 9
  • Runtime 3 Method Swizzling Objective-C Runtime(一) 简介 对象、类...
    liuyanhongwl阅读 2,187评论 0 2
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,554评论 33 466
  • 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C C...
    GrayLand阅读 1,624评论 1 10
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139