3-27.【OC】【Runtime】forwardingTargetForSelector: 与 forwardInvocation: 的区别是什么?性能上有什

7 阅读2分钟

这两个方法分别对应消息转发的第二阶段(快速路径)和第三阶段(慢速路径) 。它们最核心的区别在于:你是只想“转交任务”,还是想“深度干预过程”?


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];
}

总结

  • 快速转发 是为了效率:它就像一个分流器。
  • 标准转发 是为了功能:它就像一个手术室。