这两个方法分别对应消息转发的第二阶段(快速路径)和第三阶段(慢速路径) 。它们最核心的区别在于:你是只想“转交任务”,还是想“深度干预过程”?
1. 核心区别:控制力与灵活性
forwardingTargetForSelector: (快速转发)
- 机制:你只能返回一个新的接收者。
- 控制力:极低。你无法修改消息的参数,也无法修改返回值。这更像是一个“接力棒”的传递。
- 底层行为:Runtime 拿到你返回的对象后,直接调用
objc_msgSend(target, selector, ...)。
forwardInvocation: (标准转发)
-
机制:系统将消息封装成一个
NSInvocation对象给你。 -
控制力:极高。你可以:
- 修改调用参数。
- 修改返回值。
- 同时转发给多个对象(实现“伪多继承”或消息广播)。
- 甚至什么都不做,直接静默掉这次调用。
-
底层行为:需要先通过
methodSignatureForSelector:获取方法签名,然后由 Runtime 动态创建一个包含堆栈信息的NSInvocation。
2. 性能差异:为什么一个快一个慢?
在性能上,两者有着代差级的区别。
| 特性 | forwardingTargetForSelector: | forwardInvocation: |
|---|---|---|
| 内存开销 | 极小:仅涉及指针跳转。 | 极大:需要创建 NSInvocation 实例,并在堆上拷贝参数栈。 |
| CPU 开销 | 极低:一次简单的 C 函数调用。 | 极高:涉及方法签名解析、内存分配、寄存器状态保存。 |
| 查找次数 | 少:直接尝试新的 objc_msgSend。 | 多:需要先调签名方法,再调转发方法。 |
性能结论:
forwardingTargetForSelector的速度远快于forwardInvocation。如果只是单纯为了把消息转给别的类,永远优先选择快速转发。
3. 选择场景:我该用哪一个?
使用 forwardingTargetForSelector: 的场景:
- 插件化/模块化:一个类只负责统筹,根据 Selector 的不同,把任务分配给内部不同的子模块。
- 轻量级代理:你想让 A 类看起来拥有 B 类的所有能力。
使用 forwardInvocation: 的场景:
- 统一拦截器:比如实现一个全局的日志系统,记录每个方法的调用参数和执行时间。
- 多重响应:一份消息要发给五个不同的观察者执行。
- 热修复 (Hotfix) :需要根据服务器下发的逻辑,动态修改参数或跳过原始实现(如 JSPatch 的底层原理)。
- 防止崩溃 (Safe Mode) :如果前两步都没接住,最后在这里统一截获,避免 App 闪退。
4. 代码对比
快速转发:
Objective-C
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.otherObject respondsToSelector:aSelector]) {
return self.otherObject; // 直接甩锅,简单高效
}
return [super forwardingTargetForSelector:aSelector];
}
标准转发:
Objective-C
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 灵活度极高,可以改参数
NSString *newArg = @"Modified Parameter";
[anInvocation setArgument:&newArg atIndex:2];
// 可以转发给多人
[anInvocation invokeWithTarget:self.objA];
[anInvocation invokeWithTarget:self.objB];
}
总结
- 快速转发 是为了效率:它就像一个分流器。
- 标准转发 是为了功能:它就像一个手术室。