Objective-C 的继承链方法查找,本质上是围绕 objc_msgSend 函数展开的一场在 objc_class 结构体 之间的递归搜索。
由于 Objective-C 的动态性,它不会在编译期就固定方法的调用地址,而是通过以下步骤在运行时沿继承链向上回溯:
1. 查找的起点:isa 指针
当你执行 [student walk] 时,Runtime 首先查看 student 实例内部的 isa 指针。
- 如果是实例方法(
-),isa指向Student类对象。 - 如果是类方法(
+),isa指向Student元类对象。
2. 快速路径:Cache 命中(第一优先级)
在进入繁琐的继承链搜索之前,Runtime 会先检查当前类对象的 cache (方法缓存) 。
- 每个类都有一个哈希表记录最近调用的方法。
- 如果
walk方法就在缓存里,直接提取IMP(函数指针)并跳转执行。这极大地抵消了动态查找带来的性能损耗。
3. 慢速路径:本类方法列表(Method List)
如果缓存没中,Runtime 会进入当前类的 class_rw_t 结构体,遍历其 methods (方法列表) 。
- 编译器将
@implementation里的代码编译成了带SEL(名字)和IMP(地址)的列表。 - 找到则存入缓存并执行;找不到,则开始继承链回溯。
4. 继承链回溯:superclass 指针
这是支持继承的核心动作。如果当前类找不到方法:
- Runtime 提取当前类对象的
superclass指针。 - 跳转到父类(比如
Person类)中。 - 重复上述步骤:先看父类的
cache,再看父类的methods。 - 以此类推,一直向上追溯到 根类
NSObject。
5. 查找的终点:两个结果
- 成功:在某一级父类找到了
walk。Runtime 会将该方法地址写入最初接收消息的那个类的cache中(方便下次调用),然后执行代码。 - 失败:追溯到
NSObject且其superclass为nil后依然没找到。此时,程序并不会立即崩溃,而是进入 “消息转发(Message Forwarding)” 流程(即我之前提到的三次自救机会)。
6. 一个特殊的“横跳”:根类逻辑
在继承链的最顶端有一个非常有趣的 Smalltalk 特性:
- 根元类(Root Meta Class) 的
superclass指向 根类(Root Class,即NSObject) 。 - 效果:如果你对一个类对象调用了一个它没有的类方法,但
NSObject里有一个同名的实例方法,Runtime 最终能找到并执行它。这保证了所有类对象都能响应NSObject定义的基础行为(如description)。