由一个面试题引出的问题,为什么[self class]
和[super class]
打印的结果一样?
这里,我们使用汇编和objc源码结合的方式来研究一下。为了使汇编代码看起来简洁明了一点,去掉了打印,只保留了super调用,然后在此打上断点。
这里勾选上永远显示汇编代码
可以看到,这里rbp栈寄存器用来创建了2个临时变量,-0x8(%rbp)
存储着rdi
的值,-0x10(%rbp)
存储着rsi
的值,分别各占8个字节。
然后我们来读取一下两个寄存器中的值,发现寄存器rdi
的值和左边self
的值一样,在断点进入第13行汇编代码时,可以知道-0x20(%rbp)
存储着self
的地址,并且把这个值存入到了寄存器rdi
,-0x18(%rbp)
存储着Student
类对象的地址0x100003768
,我们知道函数调用,寄存器rdi
和rsi
一般用来传递参数。
从上面得知,寄存器rdi
(低8个字节存储着self的地址,高8个字节存储着Student类对象的地址)传递给了objc_msgSendSuper2
中的struct objc_super * super
,寄存器rsi
(存储着函数run的地址)传递给了SEL op
。
#if __OBJC2__
// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
接下来,我们看struct objc_super
的定义。
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
//通过刚才汇编可以知道,这里存储着的是当前类,不是其父类。
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
最终是在objc_msgSendSuper2
中从当前类的父类开始搜索这个方法。
总结一下,super
本质上是一个结构体,存储着对象self
和当前类对象Class
,然后会用这个结构体创建一个临时变量,最终会调用objc_msgSendSuper2
,并传递这个结构体变量和SEL
的地址。
所以[super class]
就是找到父类的class
方法,然后用self
来调用,所以会打印Student。