时间:2021-05-19
在阅读团队一项目源码时,发现Method Swizzling的写法有些瑕疵。这篇文章主要就介绍iOS Method Swizzling的正确写法应该是什么样的。
下面是iOS Method Swizzling的一种实现:
+ (void)load { Class class = [self class]; SEL fromSelector = @selector(func); SEL toSelector = @selector(easeapi_func); Method fromMethod = class_getInstanceMethod(class, fromSelector); Method toMethod = class_getInstanceMethod(class, toSelector); method_exchangeImplementations(fromMethod, toMethod);}这种写法在一些时候能正常工作,但实际上有些问题。那么问题在哪里呢?
一个例子
为了说明这个问题,我们先来假设一个场景:
@interface Father: NSObject-(void)easeapi;@end@implementation Father-(void)easeapi { //your code}@end//Son1继承自Father@interface Son1: Father@end@implementation Son1@end//Son2继承自Father,并HOOK了easeapi方法。@interface Son2: Father@end@implementation Son2+ (void)load { Class class = [self class]; SEL fromSelector = @selector(easeapi); SEL toSelector = @selector(new_easeapi); Method fromMethod = class_getInstanceMethod(class, fromSelector); Method toMethod = class_getInstanceMethod(class, toSelector); method_exchangeImplementations(fromMethod, toMethod);}-(void)new_easeapi { [self new_easeapi]; //your code}@end看样子没什么问题,Son2的方法也交换成功,但当我们执行[Son1 easeapi]时,发现CRASH了。
'-[Son1 new_easeapi]: unrecognized selector sent to instance 0x600002d701f0''
这就奇怪了,我们HOOK的是Son2的方法,怎么会产生Son1的崩溃?
为什么会发生崩溃
要解释这个问题,还是要回到原理上。
首先明确一点,class_getInstanceMethod会查找父类的实现。
在上例中,easeapi是在Son2的父类Father中实现的,执行method_exchangeImplementations之后,Father的easeapi和Son2的new_easeapi进行了方法交换。
交换之后,当Son1(Father的子类)执行easeapi方法时,会通过「消息查找」找到Father的easeapi方法实现。
重点来了!
由于已经发生了方法交换,实际上执行的是Son2的new_easeapi方法。
-(void)new_easeapi { [self new_easeapi]; //your code}可恶的是,在new_easeapi中执行了[self new_easeapi]。此时这里的self是Son1实例,但Son1及其父类Father中并没有new_easeapi的SEL,找不到对应的SEL,自然就会CRASH。
什么情况下不会有问题?
上面说了:「这种写法在一些时候能正常工作」。那么,到底什么时候直接执行method_exchangeImplementations不会有问题呢?
至少在下面几种场景中都不会有问题:
Son2中有easeapi的实现
在上例中,如果我们在Son2中重写了easeapi方法,执行class_getInstanceMethod(class, fromSelector)获取到的是Son2的easeapi实现,而不是Father的。这样,执行method_exchangeImplementations后,不会影响到Father的实现。
new_easeapi实现改进
在这个场景中,由于不会执行[self new_easeapi],也不会有问题。但这样就达不到HOOK的效果。
改进优化
推荐的Method Swizzling实现:
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL fromSelector = @selector(easeapi); SEL toSelector = @selector(new_easeapi); Method fromMethod = class_getInstanceMethod(class, fromSelector); Method toMethod = class_getInstanceMethod(class, toSelector); if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) { class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod)); } else { method_exchangeImplementations(fromMethod, toMethod); } });}可以看到,至少有两点变化:
dispatch_once
尽管dyld能够保证调用Class的load时是线程安全的,但还是推荐使用dispatch_once做保护,防止极端情况下load被显示强制调用时,重复交换(第一次交换成功,下次又换回来了...),造成逻辑混乱。
增加了class_addMethod判断
class_addMethod & class_replaceMethod
还是从定义上理解。
class_addMethod
给指定Class添加一个SEL的实现(或者说是SEL和指定IMP的绑定),添加成功返回YES,SEL已经存在或添加失败返回NO。
它有两个需要注意的点:
执行class_addMethod能避免干扰到父类,这也是为什么推荐大家尽量先使用class_addMethod的原因。显然易见,因为iOS Runtime消息传递机制的影响,只执行method_exchangeImplementations操作时可能会影响到父类的方法。基于这个原理,如果HOOK的就是本类中实现的方法,那么直接用method_exchangeImplementations也是完全没问题的。
class_replaceMethod
到此这篇关于详解iOS Method Swizzling使用陷阱的文章就介绍到这了,更多相关iOS Method Swizzling内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
IOS开发UIAlertController详解在iOS8.0后,苹果弃用了UIAlertView和UIActionSheet,转而使用UIAlertContr
微信小程序request请求后台接口php的实例详解后台php接口:http:///good/info',data:{},method:'GET',header
react中的ajax封装实例详解代码块**opts:{'可选参数'}**method:请求方式:GET/POST,默认值:'GET';**url:发送请求的地
js静态方法复制代码代码如下:functionfoo(){}//声明类foo.method=function(){}//方法体使用:foo.method()js
IOS给xcode工程关联pod的实例详解1.新建Podfile文件内容如下:platform:ios,'7.0'target:LJMediaPalyerdop