sel 和 imp
在讲Method Swizzling
前先讲一下sel 和 imp
- sel:方法编号,在
read_images
期间就就编译进入内存 - imp:函数指针地址,寻找
imp
就是寻找函数的过程
方法交换原理
每一个方法都有一个sel和imp
,方法编号SEL
通过Dispatch table表
寻找对应的imp
,,Dispatch table
是一张SEL和IMP
的对应表。
-
image.png方法编号SEL
和方法实现IMP
的对应关系
-
方法交换后
image.png -
oriSEL
->swiImp
-
swiSEL
->oriIMP
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
在方法交换时,一定要将上述代码,放进单例中进行,否则一不注意多次调用方法,才发现交换了个寂寞
案例分析
常规Method Swizzling
创建一个Person
类
- (void)viewDidLoad {
[super viewDidLoad];
Person *per = [[Person alloc] init];
[per personInstance];
}
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"方法交换---:%s", __func__);
Method oriIMP = class_getInstanceMethod(self, @selector(personInstance));
Method swiIMP = class_getInstanceMethod(self, @selector(personInstance2));
method_exchangeImplementations(oriIMP, swiIMP);
});
}
- (void)personInstance{
NSLog(@"Person类:%s",__func__);
}
- (void)personInstance2{
NSLog(@"Person类:%s",__func__);
}
image.png
看打印方法已经交换
- 接下来我们在
personInstance
方法中调用personInstance
看下是否会发生递归
- (void)personInstance2{
[self personInstance2];
NSLog(@"Person类:%s",__func__);
}
image.png
我们发现并没有发生崩溃,而是先打印的personInstance
,在打印personInstance2
分析:是因为在方法交换时personInstance
的imp
指向了personInstance2
,personInstance2
的imp
指向了personInstance
,所以当我们调用 personInstance2
时,实际上调用的是personInstance
交换父类方法
我们在创建一个student
类,继承自Person
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [[Student alloc] init];
[stu personInstance];
Person *per = [[Person alloc] init];
[per personInstance];
}
@implementation Person
- (void)personInstance{
NSLog(@"Person类:%s",__func__);
}
@end
@implementation Student
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self, @selector(personInstance));
Method swMethod = class_getInstanceMethod(self, @selector(studentInstance));
method_exchangeImplementations(oriMethod, swMethod);
});
}
- (void)studentInstance{
NSLog(@"Student类:%s",__func__);
}
@end
image.png
这个时候我们发现都打印
studentInstance
接下来我们在studentInstance
方法中,添加一行[self studentInstance];
@implementation Student
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self, @selector(personInstance));
Method swMethod = class_getInstanceMethod(self, @selector(studentInstance));
method_exchangeImplementations(oriMethod, swMethod);
});
}
- (void)studentInstance{
[self studentInstance];
NSLog(@"Student类:%s",__func__);
}
@end
image.png
原因:是因为子类,父类都调用personInstance 方法
,personInstance
其实已经指向了studentInstance
,但是在studentInstance
方法中添加[self studentInstance]
,此时父类中没有studentInstance
方法,所以报找不到该方法的错误
方法交换封装
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
网友评论