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。
Instance,Class,MetalClass的关系如图所示,下面2点我需要解释一下:
- Instance的class指针指向Class,Class的class指针指向MetalClass。简单来说可以理解为MetalClass的实例为Class,Class的实例为真正的实例Instance。
- 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 codeproject