3-9.【OC】【Runtime】一次方法调用从 objc_msgSend 开始,完整的查找路径是怎样的?

8 阅读2分钟

一次方法调用在 Objective-C 中被称为“消息传递”。当执行 [receiver message] 时,底层会转化为 objc_msgSend(receiver, selector)

为了保证性能与灵活性的平衡,Runtime 建立了一套从快到慢、从缓存到动态补偿的完整查找路径。


第一阶段:快速路径 (Fast Path)

这是性能最优的阶段,直接在汇编层级完成。

  1. 空检查与特殊处理:检查 receiver 是否为 nil(OC 支持给 nil 发消息而不崩溃)。

  2. Cache Lookup (哈希查找)

    • 通过 receiverisa 指针找到 Class
    • 在 Class 的 cache(方法缓存)中通过 selector 的哈希值快速定位。
    • 命中:直接跳转到函数实现(IMP),查找结束。
    • 未命中:进入“慢速路径”。

第二阶段:慢速路径 (Slow Path)

如果缓存没中,Runtime 会开始在内存中进行地毯式搜索。

  1. Method List 遍历

    • 在当前类的 class_rw_t 方法列表中查找。如果列表是排序过的,用二分查找;否则线性遍历。
  2. 父类递归查找

    • 如果当前类没找到,沿着 superclass 指针上溯。
    • 关键点:在查找父类的方法列表前,会先查父类的 cache(为了照顾父类的高频方法)。
    • 这个过程一直持续到 NSObject(根类)。
  3. 结果处理

    • 找到 IMP:将其填充到 receiver 类的 cache 中(方便下次秒开),然后执行。
    • 全线未命中:进入“消息转发”阶段。

第三阶段:动态补偿与转发 (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 崩溃。


总结全景表

步骤查找位置目的性能
Cachecache_t极致性能,避开列表遍历极快
Method Listclass_rw_t本类及继承链搜索
Dynamic Resolve+resolve...动态添加实现
Fast ForwardforwardingTarget...转移给其他对象较慢
Normal ForwardforwardInvocation:消息完全托管与分发最慢