3-30.【OC】【Runtime】forwardInvocation: 在真实项目中有哪些实战用途?

1 阅读3分钟

NSInvocation 的生命周期中,forwardInvocation: 是最后也是最强大的堡垒。它允许一个对象在“完全不知道如何处理某个方法”的情况下,依然能优雅地接管这个请求。

在真实项目中,这种“黑魔法”主要用于解决一些需要高度抽象、解耦或跨组件通信的场景。


1. 核心实战用途

A. 实现多重继承(聚合对象)

Objective-C 不支持多重继承,但通过 forwardInvocation:,你可以让一个对象表现得好像拥有多个类的功能。

  • 做法: 创建一个 Composite 类,内部持有多个不同类的实例。
  • 用途:Composite 收到消息时,在 forwardInvocation: 中遍历内部实例,看谁能处理这个消息并转发给它。这在构建大型插件化架构时非常有用。

B. 远程代理(Remote Proxy / RPC)

这是分布式系统(如跨进程通信)的基石。

  • 场景: 你在进程 A 中调用对象 B 的方法,但对象 B 实际上存在于服务器或另一个进程中。
  • 用途: 开发者直接调用本地定义的 Protocol 方法。由于本地没有实现,系统触发转发。你在转发中提取 NSInvocation 的参数,通过网络发送给远端,再将返回值写回 NSInvocation。这对用户来说是完全透明的。

C. 统一拦截与 AOP(面向切面编程)

在不改变原有代码的情况下,为大量方法添加统计、日志或安全校验。

  • 经典案例: Aspects 库。
  • 做法: 动态地将一个类的方法替换为 _objc_msgForward(强制触发转发机制)。然后在 forwardInvocation: 中,你可以在执行原始逻辑前后插入自定义代码(如埋点、权限检查)。

D. 响应式编程与动态代理

例如像 NSUndoManager 或某些动态表单框架:

  • 用途: 动态捕获用户的一系列操作。当用户调用一个方法时,forwardInvocation: 将该调用及其参数记录下来,以便后续实现“撤销”或“重做”功能。

2. 消息转发的三个阶段

为了更好地理解 forwardInvocation: 的位置,我们需要看它在 Runtime 查找失败后的路径:

  1. Dynamic Method Resolution: 询问类是否要动态添加一个方法。
  2. Fast Forwarding: 询问是否有一个现成的“备用对象”能接这活(forwardingTargetForSelector:)。
  3. Normal Forwarding: 最后的绝招。系统把消息打包成 NSInvocation,传给 forwardInvocation:。在这里,你可以修改参数修改返回值甚至什么都不做(防止崩溃)。

3. 为什么现在用得少了?

虽然 forwardInvocation: 极度灵活,但在现代开发(尤其是 Swift 盛行后)中受到冷落,原因有三:

  • 性能瓶颈: 创建 NSInvocation 对象并进行完全转发的开销,比普通方法调用慢几个数量级。
  • 难以调试: 这种“隐式”的逻辑会让堆栈跟踪(Stack Trace)变得非常混乱。
  • Swift 的不兼容: Swift 默认不支持 forwardInvocation:(除非对象继承自 NSObject 且方法标记为 @objc),Swift 更推崇通过 Protocol ExtensionCombine/Observation 来处理这类逻辑。

避坑指南

如果你打算在项目中使用它,记得一定要同时实现 methodSignatureForSelector:。没有正确的方法签名,系统根本无法创建 NSInvocation 对象,程序会直接抛出异常。