super的本质
我们来看一道面试题:
下列代码中Person
继承自NSObject
,Student
继承自Person
,写出下列代码输出内容。
#import "Student.h"
@implementation Student
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"----------------");
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[super superclass] = %@", [super superclass]);
}
return self;
}
@end
打印内容
Runtime-super[6601:1536402] [self class] = Student
Runtime-super[6601:1536402] [self superclass] = Person
Runtime-super[6601:1536402] ----------------
Runtime-super[6601:1536402] [super class] = Student
Runtime-super[6601:1536402] [super superclass] = Person
可以看到,无论是self
还是super
调用class
或superclass
的结构都是相同的。
为什么结果是相同的?super
关键字在调用方法的时候底层调用流程是怎样的?
我们通过一段代码来看一下super
的底层实现,为Person
类提供run
方法,Student
类中重写run
方法,方法内部调用[super run];
,将Student.m
转化为c++
代码查看其底层实现。
- (void) run
{
[super run];
NSLog(@"Student...");
}
上述代码转化为c++代码
static void _I_Student_run(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("run"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_Student_e677aa_mi_0);
}
可以发现,[super run];
转化为底层源码内部其实调用的是objc_msgSendSuper
函数。
objc_msgSendSuper
函数内传递了两个参数,__rw_objc_super
结构体和sel_registerName("run")
方法名。
__rw_objc_super
结构体内传入的参数是self
和class_getSuperclass(objc_getClass("Student"))
也就是Student
的父类Person
首先我们找到objc_msgSendSuper
函数查看其内部结构
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可以发现objc_msgSendSuper
中传入的结构体是objc_super
,我们通过源码查找objc_super
结构体查看其内部结构。
// 精简后的objc_super结构体
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接受者
__unsafe_unretained _Nonnull Class super_class; // 消息接受者的父类
/* super_class is the first class to search */
// 父类是第一个开始查找的类
};
从objc_super
结构体中可以发现receiver
消息接收者仍然为self
,superclass
仅仅是用来告知消息查找从哪一个类开始。从父类的类对象开始去查找。
我们通过一张图看一下其中的区别。
从上图中可以看到:super
调用方法的消息接收者receiver
仍然是self
,只是从父类的类对象开始去查找方法。
那么现在再回到面试题,我们知道class的底层实现如下所示:
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
class
内部实现是根据消息接收者返回其对应的类对象,最终会找到基类的方法列表中,而self
和super
的区别仅仅是self
从本类类对象开始查找方法,super
从父类类对象开始查找方法,因此最终得到的结构都是相同的。
另外我们回到run
方法内部,很明显可以发现,如果super
不是父类开始查找方法,从本来查找方法的话,就调用方法本身造成循环调用方法而crash。
同理superclass
底层实现同class
类似,其底层实现代码如下所示
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
因此得到的结果也是相同的。
objc_msgSendSuper2函数
上述OC代码转化为c++代码并不能说明super
底层调用函数就一定是objc_msgSendSuper
。
其实super
底层真正调用的函数是objc_msgSendSuper2
,我们可以通过查看汇编代码来进行验证。
- (void)viewDidLoad {
[super viewDidLoad];
}
通过断点查看其汇编调用栈
可以发现super
底层其实调用的是objc_msgSendSuper2
函数,我们来到源码中查找一下objc_msgSendSuper2
函数的底层实现,我们可以在汇编文件中找到其相关底层实现。
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
通过上面的汇编代码我们可以发现,其实底层是在函数内部调用的class->superclass
获取父类,并不是我们上面分析的直接传入的就是父类对象。
其实_objc_msgSendSuper2
内传入的结构体为objc_super2
struct objc_super2 {
id receiver;
Class current_class;
};
我们可以发现objc_super2
中除了消息接收者receiver
,另一个成员变量current_class
也就是当前类对象。
与我们上面分析的不同,_objc_msgSendSuper2
函数内其实传入的是当前类对象,然后在函数内部获取当前类对象的父类,并且从父类开始查找方法。
我们也可以通过代码验证上述结构体内成员变量究竟是当前类对象还是父类对象。下文中我们回通过另外一道面试题验证。
isKindOfClass与isMemberOfClass
首先看一下isKindOfClass、isMemberOfClass
对象方法底层实现
- (BOOL)isMemberOfClass:(Class)cls {
// 直接获取实例类对象并判断是否等于传入的类对象
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
// 向上查询,如果找到父类对象等于传入的类对象则返回YES
// 直到基类还不相等则返回NO
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
isKindOfClass、isMemberOfClass
类方法底层实现
// 判断元类对象是否等于传入的元类元类对象
// 此时self是类对象 object_getClass((id)self)就是元类
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
// 向上查找,判断元类对象是否等于传入的元类对象
// 如果找到基类还不相等则返回NO
// 注意:这里会找到基类
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
通过上述源码分析我们可以知道:
isMemberOfClass
判断左边是否刚好等于右边类型
isKindOfClass
判断左边或者左边类型的父类是否刚好等于右边类型
类方法内部是获取其元类对象进行比较