super的本质
- 新建一个Student类继承至Person,Student实现一个方法调用父类的方法。
Student *s = [[Student alloc] init];
[s test1];
@interface Student : Person
- (void)test1;
@end
- (void)test1
{
[super test];
}
- 通过
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc
命令得到c++代码
static void _I_Student_test1(Student * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("test"));
//去掉一些强转
objc_msgSendSuper((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("test"));
}
-
需要注意的是,通过断点看汇编执行流程,发现执行的并不是objc_msgSendSuper,而是objc_msgSendSuper2
image -
我们通过clang命令得到的c++代码只能作为参考,大部分都是正确的。
-
通过查看objc4源码发现objc_msgSendSuper2传入的2个参数应该是
- struct objc_super2 {
id receiver; //receiver是消息接收者
Class current_class; //current_class是receiver的Class对象
}结构体 -
SEL
image
- struct objc_super2 {
-
super调用的本质就是直接从父类的类对象或元类对象去找方法的实现,消息接收者还是自身。
Method Swizzling的坑与应用
-
Method Swizzling的意思就是方法交换,主要作用是在运行时交换方法的实现(IMP)
Method Swizzling交换原理.png - Method Swizzling涉及的方法
- class_getInstanceMethod:获取实例方法
- class_getClassMethod:获取类方法
- method_getImplementation:获取一个方法的实现
- method_setImplementation:设置一个方法的实现
- method_getTypeEncoding:获取方法实现的编码类型
- class_addMethod:添加方法实现
- class_replaceMethod:如果方法不存在,就添加一个,如果存在就将IMP替换
// 官方文档给出的描述
This function behaves in two different ways:
If the method identified by name does not yet exist, it is added as if class_addMethod were called. The type encoding specified by types is used as given.
If the method identified by name does exist, its IMP is replaced as if method_setImplementation were called. The type encoding specified by types is ignored.
此函数的行为有两种不同的方式:
如果通过名称标识的方法尚不存在,则将其添加为如同调用class_addMethod一样。 给定使用由类型指定的类型编码。
如果确实存在按名称标识的方法,则将其IMP替换,就像调用method_setImplementation一样。 由类型指定的类型编码将被忽略。
- method_exchangeImplementations:交换两个方法的实现
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
method-swizzling使用过程中的一次性问题
- 一般情况下我们会在load方法里面实现方法交换,但是我们可能会主动调用load方法之后方法又会被交换回来了,这样就可能会导致方法没有交换。
- 这种情况我们的解决方案一般是将它设计成单列
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
交换方法
});
}
自己没有实现,父类实现的问题
- 父类实现了
instancetypeMeth1
,子类没有实现,在子类的分类中交换方法
@interface Person : NSObject
- (void)instancetypeMeth1;
@end
@implementation Person
- (void)instancetypeMeth1
{
NSLog(@"%s",__func__);
}
@end
@interface Student (XQ)
@end
@implementation Student (XQ)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[XQRuntimeTool xq_methodSwizzlingWithClass:self oriSEL:@selector(instancetypeMeth1) swizzledSEL:@selector(stu_instancetypeMeth1)];
});
}
- (void)stu_instancetypeMeth1
{
[self stu_instancetypeMeth1];
NSLog(@"%s",__func__);
}
@end
- 在viewController里面调用
Person
实例的instancetypeMeth1
方法,会发生崩溃,原因是因为[self stu_instancetypeMeth1];
会调用Person
的stu_instancetypeMeth1
方法,而Person
并没有实现该方法
Student *s = [Student new];
[s instancetypeMeth1];
Person *p = [Person new];
[p instancetypeMeth1];
image.png
- 解决IMP找不到的问题,就是在交换方法的时候,尝试给类添加要交换的方法,如果添加成功,即类中没有这个方法,则通过class_replaceMethod进行替换,其内部会调用class_addMethod进行添加。如果添加不成功,即类中有这个方法,则通过method_exchangeImplementations进行交换
+ (void)xq_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 尝试添加你要交换的方法
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
method_exchangeImplementations(oriMethod, swiMethod);
}
}
- 在上面的基础上,如果父类和子类都没有实现原方法,再调用的时候就会出现递归死循环,原因是oriMethod是空,交换一个空的实现是没有效果的
- (void)stu_instancetypeMeth1
{
[self stu_instancetypeMeth1];//在这里相当于自己调用自己
NSLog(@"%s",__func__);
}
- 因此,我们再交换方法的时候,还要加上对原IMP的判断,最终的代码如下
+ (void)xq_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){
NSLog(@"来了一个空的 imp");
}));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :instanceMethod1 (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle instanceMethod1(imp) -> swizzledSEL
//oriSEL:instanceMethod1
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);
}
}
网友评论