在 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 查找失败后的路径:
- Dynamic Method Resolution: 询问类是否要动态添加一个方法。
- Fast Forwarding: 询问是否有一个现成的“备用对象”能接这活(
forwardingTargetForSelector:)。 - Normal Forwarding: 最后的绝招。系统把消息打包成
NSInvocation,传给forwardInvocation:。在这里,你可以修改参数、修改返回值甚至什么都不做(防止崩溃)。
3. 为什么现在用得少了?
虽然 forwardInvocation: 极度灵活,但在现代开发(尤其是 Swift 盛行后)中受到冷落,原因有三:
- 性能瓶颈: 创建
NSInvocation对象并进行完全转发的开销,比普通方法调用慢几个数量级。 - 难以调试: 这种“隐式”的逻辑会让堆栈跟踪(Stack Trace)变得非常混乱。
- Swift 的不兼容: Swift 默认不支持
forwardInvocation:(除非对象继承自NSObject且方法标记为@objc),Swift 更推崇通过 Protocol Extension 或 Combine/Observation 来处理这类逻辑。
避坑指南
如果你打算在项目中使用它,记得一定要同时实现 methodSignatureForSelector:。没有正确的方法签名,系统根本无法创建 NSInvocation 对象,程序会直接抛出异常。