Method Swizzling 问题
网上关于使用这个方法的教程很多,但是跟着教程走一遍发现一些问题,下面我来说说我对这个方法的理解.
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
上面是核心代码,目的是交换两个方法实现.
SEL originalSelector 理解相当于源方法的Name
SEL swizzledSelector 可以理解为调换的方法Name
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
要先尝试添加originalSelector是为了做一层保护,因为如果这个类没有实现originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法.这时候违背了我们交换方法的意愿.
现在具体的代码讲解完了,实践一下。举一个老生常谈的Logging方法,埋点问题。主要是探索用户习惯,对 App 的用户行为进行追踪和分析。
建一个UIViewController的类别UIViewController+Logging
+ (void)load{
swizzleMethod([self class], @selector(viewWillAppear:), @selector(new_viewWillAppear:));
}
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)new_viewWillAppear:(BOOL)animated{
}
+load: 是个特例,当一个类被读到内存的时候, runtime 会给这个类及它的每一个类别都发送一个 +load: 消息。
注意: new_viewWillAppear里并没有写方法,正常应该是:
- (void)new_viewWillAppear:(BOOL)animated{
[self new_viewWillAppear:animated];
}
这时候调用自身方法实际是调用的原viewWillAppear的IMP,因为上面已经将两个方法的IMP交换了。
跑一下代码,发现会先走的是子类的viewWillAppear方法, 然后才会走你类别的new_viewWillAppear方法.你发现你晕了,我已经交换了IMP啊,为啥还会走子类的viewWillAppear,问题出现在:
+ (void)load{
swizzleMethod([self class], @selector(viewWillAppear:), @selector(new_viewWillAppear:));
}
中的[self class],打印它,它是:UIViewController。
原来我们hook了父类的viewWillAppear,交换的实际是UIViewController 中的viewWillAppear方法,将子类viewWillAppear中的[super viewWillAppear:animated]
删除,你会发现,此时类别中的- (void)new_viewWillAppear:(BOOL)animated
不会走了。
现在你会问:既然父类的方法被类别中的替换了,那么子类也应该也被替换.
答案:
方法并不会重写,只会在method list里面插入的位置靠前,category写一个跟类里面名字 方法签名一模一样的方法 还可以有办法可以调用到类原来的这个方法。
注意:在交换方法的时候一定要注意,hook 的是哪个类,这个类是不是你想要交换的方法的那个类。
欢迎各位同学指正错误,我会努力及时改正!