Method Swizzling
2020-08-29 本文已影响0人
阿狸小朋友
Objective-C 中的 Hook 又被称作 Method Swizzling。
较好的开源项目 RSSwizzle 和 jrswizzle
项目中常用的方法交换写法如下:
版本1
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(method_original:);
SEL swizzledSelector = @selector(method_swizzle:);
//⚠️下面的originalMethod和swizzledMethod类似于值传递
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
版本2
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(method_original:);
SEL swizzledSelector = @selector(method_swizzle:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
常见的以上两种版本,版本2是版本1的缩略版,正常情况下不会有问题,但是下面这种情况会有问题。
复现条件:
1. B继承自A
2. B中没有method_original
方法的实现,A有method_original
方法的实现
3. 在B类中进行method_original
和method_swizzle
交换
结果:
A在调用method_original
时,实际上会调用B的method_swizzle
,此时会出现方法找不到的错误([A method_swizzle]
)
以上有个很重要的知识点:
如果在子类B中 class_addMethod
添加成功,那么originalMethod
和originalMethod2
就不想等了
//子类B的load方法
+ (void)load
{
//可以理解成值的传递,指向的父类的方法
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
//子类B中添加成功
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
//originalMethod2指向子类新添加的方法,originalMethod还是指向父类的方法
Method originalMethod2 = class_getInstanceMethod(aClass, originalSelector);
}