3-15.【OC】【Runtime】objc_msgSendSuper 和 objc_msgSend 在查找起点上有什么区别?

5 阅读2分钟

在 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

为什么?

  1. [self class] :调用 objc_msgSend,从 Child 类查找 class 方法。Child 没实现,去 Parent 找,还没实现,去 NSObject 找。NSObject 的实现逻辑是 return object_getClass(self),此时 selfChild 实例,所以返回 Child
  2. [super class] :调用 objc_msgSendSuper。查找起点是 Parent。由于 Parent 也没实现 class,最终还是在 NSObject 里找到了实现。重点来了: 虽然从父类开始找,但消息的接收者(receiver)依然是 selfChild 实例)。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 是如何通过寄存器传递那个特殊的结构体的吗?