在 Objective-C 中,self 是一个对象指针,而 super 并不是一个真正的对象,它只是一个编译器指令。
它们在底层的区别在于:查找方法时“第一站”在哪里。
1. 查找起点的对比
我们可以通过下表一眼看出两者的核心区别:
| 函数 | 调用场景 | 接收者 (receiver) | 查找起点 (Search Start) |
|---|---|---|---|
objc_msgSend | [self message] | self (当前对象) | 当前对象的类 (isa) |
objc_msgSendSuper | [super message] | self (当前对象) | 父类 (superclass) |
2. 底层结构揭秘
当编译器看到 [super message] 时,它会悄悄地构造一个名为 objc_super 的结构体。
Objective-C
// 简化版的 objc_super 结构体
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 依然是当前的 self
__unsafe_unretained _Nonnull Class super_class; // 编译器指定的父类
};
objc_msgSend:直接拿着receiver去它的类缓存和方法列表里找。objc_msgSendSuper:它跳过了receiver自己的类,直接去objc_super->super_class指向的那个类里开始找。
3. 一个著名的“陷阱”题
理解了起点区别,你就能秒杀这道经典的面试题:
在 Child 类(继承自 Parent)的 init 方法中,执行以下代码会输出什么?
Objective-C
NSLog(@"self class: %@", [self class]);
NSLog(@"super class: %@", [super class]);
答案是:两者都输出 Child。
为什么?
[self class]:调用objc_msgSend,从Child类查找class方法。Child没实现,去Parent找,还没实现,去NSObject找。NSObject的实现逻辑是return object_getClass(self),此时self是Child实例,所以返回Child。[super class]:调用objc_msgSendSuper。查找起点是Parent。由于Parent也没实现class,最终还是在NSObject里找到了实现。重点来了: 虽然从父类开始找,但消息的接收者(receiver)依然是self(Child实例)。NSObject还是执行return object_getClass(self),结果依然是Child。
4. 为什么要多此一举?
既然 receiver 没变,为什么要设计 objc_msgSendSuper?
如果没有它,当你在子类重写父类方法并想调用 [super method] 时,就会陷入无限死循环:
self调用method-> 发现重写了 -> 执行子类实现。- 子类实现里又调用
[self method]-> 又回到子类实现...
objc_msgSendSuper 的存在,本质上是告诉 Runtime: “我知道我自己重写了这个方法,请直接去我爸爸那里找,不要再看我了。”
总结
objc_msgSend:从当前类开始找。objc_msgSendSuper:从父类开始找。- 相同点:两者的
receiver都是self。
如果你对底层的这种“偷梁换柱”感兴趣,想了解一下在 ARM64 汇编层级,objc_msgSendSuper 是如何通过寄存器传递那个特殊的结构体的吗?