不会。
如果“动态方法解析”成功(即 resolveInstanceMethod: 或 resolveClassMethod: 返回了 YES),Runtime 会认为该方法现在已经存在,并重新尝试查找并执行该方法。由于你已经通过 class_addMethod 注入了实现,这次查找会直接命中,从而正常执行函数,流程到此结束。
只有当动态解析返回 NO,或者返回了 YES 但你并没有真正添加对应的 IMP 时,系统才会进入后续的**消息转发(Message Forwarding)**流程。
1. 消息处理的“全景流水线”
为了让你看清动态解析的位置,我们可以把整个过程想象成一个三关的挑战。一旦某一关通过,后面的关卡就会被直接跳过。
-
第一关:动态方法解析 (Dynamic Method Resolution)
- 询问类:“你能临时变出这个方法吗?”
- 如果
YES重新执行方法,流程结束。
-
第二关:快速转发 (Fast Forwarding)
- 询问对象:“既然你不会,有人能替你做吗?”
- 如果返回了非
nil的转发目标 由新目标执行,流程结束。
-
第三关:标准转发 (Normal Forwarding)
- 询问对象:“请把这整件事打包,我看看怎么处理。”
- 如果处理了
NSInvocation流程结束;否则,doesNotRecognizeSelector:抛出崩溃。
2. 为什么解析成功后要“重新查找”?
这是底层实现的一个精妙之处。当你返回 YES 时,Runtime 的底层汇编代码会重置查找指针,再次通过 objc_msgSend 的逻辑去搜一遍。
- 性能考虑:因为你刚刚用
class_addMethod把方法塞进了类的方法列表里,下次调用这个方法时,它就会像普通方法一样被缓存到cache_t。 - 无感执行:重新查找确保了动态添加的方法能够像原生方法一样,拥有完整的缓存加速和执行链路,而不需要每次都走昂贵的“转发”逻辑。
3. 注意一个常见的“坑”
如果你在 resolveInstanceMethod: 里返回了 YES,但是忘记调用 class_addMethod:
- Runtime 收到
YES,于是满怀信心去重新查找。 - 结果发现还是找不到
IMP。 - 这时,系统会意识到你“骗”了它,流程会再次进入动态解析(有些版本会直接跳过)或者直接进入消息转发。
- 如果循环处理不好,甚至可能导致死循环或直接崩溃。
4. 总结对比:动态解析 vs 消息转发
| 特性 | 动态方法解析 | 消息转发 (Fast/Normal) |
|---|---|---|
| 触发时机 | 第一时间(找不到就触发) | 动态解析失败后触发 |
| 性能开销 | 低(后续调用会进入缓存) | 高(每次都需要创建转发对象或 Invocation) |
| 核心目的 | 为当前类添加新能力 | 将压力转嫁给其他对象或处理逻辑 |
| 代码位置 | 作用于“类”级别 (+ 方法) | 作用于“实例”级别 (- 方法) |