objc_supre与objc_msgSendSuper
objc_super 和 objc_super2
它们的区别在于第二个成员:
objc_super
:super_class //receiverClass的父类objc_super2
: current_class//receiverClass(消息接收者的class对象)
1 | // message.h(objc4) |
objc_msgSendSuper()和objc_msgSendSuper2()
objc_msgSendSuper2()
函数所接收的第一个参数应该为objc_super2
而非objc_super
1 | // message.h(objc4) |
self和super
self
OC方法都带有两个隐式参数:(id)self和(SEL)_cmd
self是一个对象指针,指向当前方法的调用者/消息接收者
如果是实例方法,它就是指向当前类的实例对象;
如果是类方法,它就指向当前类的类对象当使用self调用方法的时候,底层会转换为objc_msgSend()函数的调用,该函数会从
当前消息接收类
中开始查找方法的实现。
super
- super是一个编译器指令
- 当使用super调用方法的时候,底层会转换为
objc_msgSendSuper2()
函数的调用,该函数会从当前消息接收者类的父类中开始查找方法的实现。
super本质
使用clang将以下OC代码转换为C++代码:
1 | [super viewDidLoad] |
1 | // 转换为 C++ |
可以看到,Runtime 将super
转换为objc_msgSendSuper()
函数的调用,参数为objc_super
和SEL
LLVM & 中间代码
为什么前面说super会转换为objc_msgSendSuper2()函数的调用呢?
因为转成的C++的实现和真正的底层实现是有差异的,LLVM编译器会将“OC代码”先转成“中间代码(.ll)”再转成“汇编、机器代码”,该中间代码非C/C++
可以使用以下命令行指令生成中间代码:clang -emit-llvm -S main.m
通过汇编验证
将ViewController.m文件转换成汇编代码进行验证
查看第 18 行代码即[super viewDidLoad]转换成的汇编代码
以上可以看到,[super viewDidLoad]
底层实际上是转换成了objc_msgSendSuper2()
函数的调用而非objc_msgSendSuper()
。
源码
当使用super调用方法的时候,底层会转换为objc_msgSendSuper2()
函数的调用,该函数接收两个参数struct objc_super2
和 SEL
1 | struct objc_super2 { |
objc_msgSendSuper2()函数内部会通过current_class的superclass指针拿到它的父类,从父类开始查找方法的实现。忽略“从 receiverClass 中查找方法的过程”,对应下图就是直接从第 5 步开始
要注意receiver消息接收者还是子类对象
,而不是父类对象,只是查找方法实现的范围变了。
几个问题
Q:调用以下 init 方法的打印结果是什么?
1 | @interface HTPerson : NSObject |
1 | [self class] = HTStudent |
class和superclass方法的实现在 NSObject 类中,可以看到它们的返回值取决于receiver
1 | + (Class)class { |
[self class]
是从receiverClass
开始查找方法的实现,如果没有重写的情况,则会一直找到基类 NSObject,然后调用。[super class]
是从receiverClass->superclass
开始查找方法的实现,如果没有重写的情况,则会一直找到基类 NSObject,然后调用。
由于receiver相同,所以它们的返回值是一样的。