标准的Method Swizzling定义
之前,我在Category中实现Method Swizzling 时,会使用如下方式定义:
+ (void)load {
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(cz_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
但是在看公司项目代码时是下面这种方式实现的:
void MoerSwizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL didAddMethod =
class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
比我之前那种方式在方法交换前多了两个函数调用class_addMethod()和class_replaceMethod。刚开始搞不懂为什么要这样处理,然后就开始写代码自己测试,最终搞明白了。举个不太恰当的例子,比如定义了一个BaseViewController,继承自UIViewController,在BaseViewController中没有实现viewWillAppear:方法,但是此时又定义了一个BaseViewController的Category,在这个Category中,定义了一个私有的xx_viewWillAppear:方法,并且在load方法中实现了按照上文第一种方式实现了Method Swizzling。此时,由于BaseViewController中没有定义viewWillAppear:方法,所以通过Method originalMethod = class_getInstanceMethod(class, originalSelector);该方式得到的Method是从父类UIViewController中拿到的。在method_exchangeImplementations后,会将Category中定义的方法实现xx_viewWillAppear:与父类UIViewController中的viewWillAppear:的实现进行交换。此后,当你再次调用UIViewController中的viewWillAppear时,都是执行xx_viewWillAppear:的方法实现。
而“摩尔”中的方式则会避免的这种情况的发生。首先如果BaseViewController:中没有实现viewWillAppear:方法,则Method originalMethod = class_getInstanceMethod(cls, originalSelector); 通过该方式的得到的Method是从父类UIViewController中拿到的,然后BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));这个方法的作用就是如果本类中没有实现viewWillAppear:,则会将父类拿到的方法实现添加到本类的方法列表中,并返回YES,如果本类已经有viewWillAppear:的方法实现,则返回NO,之后用便会执行正常的方法交换流程。
总结:标准的Method Swizzling实现
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
之所以加dispatch_once是为了防止有人手动调用load方法。
参考文章:
method swizzling