当一个对象无法识别发送给它的消息时,Objective-C Runtime 提供了三道防线来挽救。这三个阶段是由浅入深、性能开销从小到大的过程。如果这三个阶段都走完了仍未处理,程序就会抛出 unrecognized selector sent to instance 异常并崩溃。
第一阶段:动态方法解析 (Dynamic Method Resolution)
这是最先被触发的阶段。系统会询问当前的类:“你能临时为这个方法提供一个实现(IMP)吗?”
- 核心方法:
+resolveInstanceMethod:(实例方法) 或+resolveClassMethod:(类方法)。 - 做法:开发者可以在此方法内部调用
class_addMethod动态注入函数实现。 - 结果:如果返回
YES,Runtime 会重新启动消息寻找流程;如果返回NO,进入下一阶段。 - 特点:性能开销最低,因为一旦添加成功,该方法会被缓存,后续调用就像普通方法一样快。
第二阶段:快速转发 (Fast Forwarding)
如果第一阶段没能解决问题,系统会询问对象:“既然你处理不了,有没有其他对象(Target)能处理?”
- 核心方法:
-forwardingTargetForSelector:。 - 做法:直接返回一个能够响应这个
SEL的其他对象。 - 结果:如果返回一个非
nil对象,系统会将消息转发给该对象;如果返回nil,则进入最终阶段。 - 特点:逻辑简单,只需要转移接力棒。这被称作“快速”是因为它不需要创建复杂的
NSInvocation对象。
第三阶段:标准转发 (Normal Forwarding)
这是最后也是最灵活的一道防线。系统会把消息的所有细节(方法名、参数、目标)打包成一个 NSInvocation 对象。
-
核心方法:
-methodSignatureForSelector::必须先返回一个正确的方法签名(包含返回值和参数类型)。-forwardInvocation::拿到打包好的NSInvocation后,你可以在这里随心所欲地处理(修改参数、修改返回值、转发给多个对象、或者直接静默处理)。
-
结果:如果处理了,流程结束;如果未处理,系统执行
doesNotRecognizeSelector:。 -
特点:性能开销最高,但灵活性最强。
总结对比
| 阶段 | 关键方法 | 核心逻辑 | 性能 |
|---|---|---|---|
| 1. 动态解析 | +resolveInstanceMethod: | 动态添加 IMP | 极高 |
| 2. 快速转发 | -forwardingTargetForSelector: | 找个“替死鬼”执行 | 较高 |
| 3. 标准转发 | -forwardInvocation: | 彻底的逻辑重组 | 较低 |
实际应用场景建议:
- 热修复/动态注入:优先选择第一阶段。
- 多重继承模拟/代理模式:通常选择第二阶段。
- 防止崩溃/无埋点统计/消息拦截器:通常选择第三阶段,因为它能完全控制整个消息包。