【迁移】MethodSwizzling(二)

coding 的演示功能不让用,原来搭建的博客访问不了了。索性将全部博客迁移到简书,这篇是旧文章,欢迎大家以后来简书看我的博客

上一篇博客中我们简单介绍了Method Swizzling,看过上一篇文章也许大家会觉得Method Swizzling is so easy,不过事情没那么简单。有很多细节任然需要我们注意!!!

细节

在ARC之前,我们经常备受内存管理的困扰,有时候一些对象莫名其妙就被释放了,都不知道在哪儿释放的。在任何对象dealloc的时候都打印一个log,这样只要看log就知道在哪儿被释放了。由于对象的类众多,重写dealloc工作量巨大,所以我们决定用Swizzling。

上代码~~~

@implementation NSObject(Swizzling)
void methodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
    Method originMethod = class_getInstanceMethod(class, originSel);
    Method overrideMethod = class_getInstanceMethod(class, overrideSel);
    
    if (class_addMethod(class,
                        originSel,
                        method_getImplementation(overrideMethod),
                        method_getTypeEncoding(originMethod)))
    {
        /** case1:NSMutableDictionary中没有-setObject:forKey:的实现 */
        class_replaceMethod(class,
                            overrideSel,
                            method_getImplementation(originMethod),
                            method_getTypeEncoding(originMethod));
    }else{
        /** case2:NSMutableDictionary中有-setObject:forKey:的实现   */
        method_exchangeImplementations(originMethod, overrideMethod);
    }
}

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        methodSwizzling([NSObject class], @selector(dealloc), @selector(swzzling_dealloc));
    });
}

- (void)swzzling_dealloc
{
    printf("我在这里被释放了");
    [self swzzling_dealloc];
}
@end

例子必须在非ARC下运行,因为@selector(dealloc)在ARC下编译器会报错

例子有些简单和幼稚,现实中可能没有这样的需求,不过这都不是重点,重点是……

1. +load VS +initialize

有没有人想过为什么Swizzling的代码要放在+load中?why?

=> 因为Swizzling的代码必须要在方法执行之前,否则方法都执行了,再Swizzling就没有意义了,而+load方法是main函数之前调用的(class添加runtime中的时候调用),所以可以保证在方法执行前Swizzling……

+initialize方法也可以保证在方法执行前调用,那是否可以将Swizzling的代码放在这个里面?

=> 放在+initialize里面大多数时候是不会有问题的,不过这样做会有风险。我们知道,+initialize方法是在Class接到第一个消息之前执行,也就是说,多个有继承关系的类,他们的+initialize方法执行的顺序取决于具体的代码,哪个类先接收到第一个消息,哪个类的+initialize方法就先执行。如果这几个类都对同一个Method执行了Swizzling,这就会导致他们行为的不确定性,我们不知道哪个Swizzling先执行,从而可能会产生隐藏的难以发现的bug。而+load执行的顺序是确定的。父类的+load先执行,之后才执行子类

2. dispatch_once

显而易见,如果多个线程同时执行同一段Swizzling的代码,有可能造成混乱,产生我们不希望的结果,所以一般Swizzling的代码都需要放在dispatch_once中,不过由于系统保证了+load方法不会同时执行多次,所以在+load中不加dispatch_once也影响不大,不过为了养成良好的代码系统,加上dispatch_once是最好的

3. Swizzling Class Method

一直以来,我们都仅仅是对InstanceMethod(实例方法)进行Swizzling,如果我们需要对Class Method(类方法)进行Swizzling,那该怎么做呢?
首先我们来看看答案吧:

void classMethodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
    Class metalClass = object_getClass(class);
    methodSwizzling(metalClass, originSel, overrideSel);
}

我们可以看到,只需要将原来的Class替换成metalClass即可对ClassMethod进行Swizzling。
but why?
要解释这个,首先我们需要了解一下Class。

Class 关系图
Class 关系图

Instance,Class,MetalClass的关系如图所示,下面2点我需要解释一下:

  1. Instance的class指针指向Class,Class的class指针指向MetalClass。简单来说可以理解为MetalClass的实例为Class,Class的实例为真正的实例Instance。
  2. MetalClass的结构与Class的结构完全相同,他们的区别只是Class中存放实例方法,MetalClass中存放类方法。

所以首先,object_getClass()方法传入一个实例对象,返回实例对象的Class。由1可知,传入class,返回MetalClass。

由于Class中只存放实例方法,所以要对类方法进行Swizzling必须要在MetalClass上进行,并且MetalClass和Class的结构完全相同,所以只需要将原来的Class替换成metalClass即可对ClassMethod进行Swizzling。

Danger

大家可能都知道Method Swizzling是有风险的,但是具体风险在哪里,可能大家就不太明确了。我查找了一些资料,收集了一些Swizzling存在风险的地方,如果大家还发现其他的风险,请联系我。

1.Refused by AppStore

危险系数:★★★★★
遭遇概率:★☆☆☆☆
曾经出现过由于Swizzling系统API而被AppStore拒绝的事情,不过我在网上查了很长时间,这个事件仅发生了一次,并且后面的人做同样的事并没有遭到拒绝,这个应该也和审核的人有关。一般情况下应该是不会被拒的,所以这个危险系数极高,但是遭遇概率也非常低。事件的详细情况看这里

2.Class Cluster

危险系数:★★★★☆
遭遇概率:★★☆☆☆
类族的概念在上一篇博客里说起过。简单的说,类族就是表面上我们似乎只是在用一个类,例如NSString(NSNumber,NSArray,NSDictionary等类似),实际我们使用的是他们的子类,如:__NSCFConstantString,NSPathStore2,NSBigMutableString等,由于这些子类并不公开,我们不知道到底有多少子类,以后还会不会增加子类,从而Swizzling的时候无法对所有子类覆盖,导致可能有的情况下使用NSString的方法是Swizzling过的,有的情况下调用的是未Swizzling的方法。所以对于这种情况,建议放弃使用Method Swizzling。

3. Swizzling changes the method's arguments

危险系数:★★☆☆☆
遭遇概率:★★★★☆
Swizzling改变了方法的参数。我们知道,当我们调用一个方法时[obj test],系统会将其转化为消息发送objc_msgSend(obj,@selector(test)),并将obj和SEL传送到方法中,在方法中,可以通过_cmd获取传入的SEL,通过刚刚的传统Swizzling方法Swizzling过后,传入原函数的SEL改变了,若原函数中使用了_cmd,就有可能发生错误。幸好,这个风险是可以避免的,通过以下的修改即可避免这个风险。

- (void)swzzling_dealloc
{
    printf("我在这里被释放了");
//    [self swzzling_dealloc];
    IMP originImp = class_getMethodImplementation([self class], @selector(swzzling_dealloc));
    originImp(self,_cmd);
}

我们直接调用IMP,将正确的_cmd传入其中即可。

直接调用IMP的写法有些粗鲁,过几天有时间可以研究一下将其封装成一个方法,再过来更新
更新:由于IMP传入的参数是可变长参数,因此封装的方法传入的参数必须也为可变长参数,然而目前无法做到将可变长参数专递给另一函数,所以这个地方暂时无法封装成一个方法。如果你有什么其他办法可以做到,请联系我

4.others

这里还列出了一些其他的风险,念茜大神在这里对他进行了翻译,我就不在这里赘述了。感兴趣的朋友可以看看,里面还提供了另外一种Method Swizzling的方法,同样也可以解决3中这个问题。

参考

Method Swizzling in nshipster

Method Swizzling in codeproject

What are the Dangers of Method Swizzling in Objective C?

Objective-C的hook方案(一): Method Swizzling

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,679评论 0 9
  • 前言 到了今天终于要"出院"了,要总结一下住院几天的收获,谈谈Runtime到底能为我们开发带来些什么好处。当然它...
    一缕殇流化隐半边冰霜阅读 23,351评论 56 317
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,544评论 33 466
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 913评论 0 6