一次方法调用在 Objective-C 中被称为“消息传递”。当执行 [receiver message] 时,底层会转化为 objc_msgSend(receiver, selector)。
为了保证性能与灵活性的平衡,Runtime 建立了一套从快到慢、从缓存到动态补偿的完整查找路径。
第一阶段:快速路径 (Fast Path)
这是性能最优的阶段,直接在汇编层级完成。
-
空检查与特殊处理:检查
receiver是否为nil(OC 支持给 nil 发消息而不崩溃)。 -
Cache Lookup (哈希查找) :
- 通过
receiver的isa指针找到 Class。 - 在 Class 的
cache(方法缓存)中通过selector的哈希值快速定位。 - 命中:直接跳转到函数实现(IMP),查找结束。
- 未命中:进入“慢速路径”。
- 通过
第二阶段:慢速路径 (Slow Path)
如果缓存没中,Runtime 会开始在内存中进行地毯式搜索。
-
Method List 遍历:
- 在当前类的
class_rw_t方法列表中查找。如果列表是排序过的,用二分查找;否则线性遍历。
- 在当前类的
-
父类递归查找:
- 如果当前类没找到,沿着
superclass指针上溯。 - 关键点:在查找父类的方法列表前,会先查父类的
cache(为了照顾父类的高频方法)。 - 这个过程一直持续到
NSObject(根类)。
- 如果当前类没找到,沿着
-
结果处理:
- 找到 IMP:将其填充到
receiver类的cache中(方便下次秒开),然后执行。 - 全线未命中:进入“消息转发”阶段。
- 找到 IMP:将其填充到
第三阶段:动态补偿与转发 (Message Forwarding)
如果方法确实不存在,Runtime 并不急着崩溃,而是给了你三次“自救”的机会。
1. 动态方法解析 (Dynamic Method Resolution)
- 调用
+resolveInstanceMethod:(或+resolveClassMethod:)。 - 你可以做什么:在这个阶段用
class_addMethod临时把方法补上去。如果补救成功,流程重新回到“快速路径”。
2. 快速转发 (Fast Forwarding)
- 调用
-forwardingTargetForSelector:。 - 你可以做什么:返回一个“备胎”对象(另一个能处理该消息的对象)。Runtime 会把消息转给它,你的任务就完成了。
3. 标准转发 (Normal Forwarding)
这是最后、也是开销最大的机会。
- 签名准备:调用
-methodSignatureForSelector:获取方法参数和返回类型。 - 打包发送:调用
-forwardInvocation:。 - 你可以做什么:你会拿到一个
NSInvocation对象,它封装了消息的所有细节。你可以修改它的参数、改发给多个人,甚至直接吞掉这个消息不处理。
终点:无法处理 (Does Not Recognize Selector)
如果以上所有机会你都错过了,Runtime 最终会调用 -(void)doesNotRecognizeSelector:,抛出那个著名的异常:
unrecognized selector sent to instance ...,此时 App 崩溃。
总结全景表
| 步骤 | 查找位置 | 目的 | 性能 |
|---|---|---|---|
| Cache | cache_t | 极致性能,避开列表遍历 | 极快 |
| Method List | class_rw_t | 本类及继承链搜索 | 中 |
| Dynamic Resolve | +resolve... | 动态添加实现 | 慢 |
| Fast Forward | forwardingTarget... | 转移给其他对象 | 较慢 |
| Normal Forward | forwardInvocation: | 消息完全托管与分发 | 最慢 |