3-29.【OC】【Runtime】NSInvocation 在 Runtime 中解决了什么问题?为什么 Swift 没有等价物?

5 阅读3分钟

在 Objective-C 的世界里,NSInvocation 是一个非常强大但底层的存在。简单来说,它是对“消息转发”过程的一种静态呈现

以下是它解决的核心问题以及为什么在 Swift 的设计哲学中它不再受宠。


1. NSInvocation 解决了什么问题?

在 Objective-C 这种高度动态的语言中,调用一个方法本质上是发送一条消息。通常我们使用 [object method],但这在编译时就确定了参数和返回值。

NSInvocation 解决的是**“如何在运行时完全动态地构造一个调用”**。

核心功能:

  • 参数解耦: 它可以把一个方法的接收者、选择器(Selector)、以及所有参数全部封装成一个对象
  • 延迟执行: 因为调用被对象化了,你可以把它存在数组里、传给其他线程,或者在未来的某个时刻触发。
  • 消息转发(Message Forwarding): 当对象收到一个无法识别的消息时,Runtime 会把这消息包装成 NSInvocation。开发者可以截获它,修改参数、修改目标,甚至转发给另一个对象。

比喻: 普通的方法调用就像是直接打电话;而 NSInvocation 就像是写一封挂号信。信里写明了收件人、地址、具体内容和附件。你可以把这封信寄走,也可以压在枕头下过两天再寄,甚至可以拆开信封把里面的内容改了再寄。


2. 为什么 Swift 没有等价物?

Swift 的设计目标是安全(Safe)快速(Fast)表现力(Expressive)NSInvocation 恰恰在安全和速度上表现不佳。

A. 类型安全 (Type Safety)

NSInvocation 是类型不安全的。它操作的是裸指针(void *),你需要手动管理参数的内存索引。如果类型对不上,程序会直接崩溃。Swift 倾向于在编译期就确定一切,利用**泛型(Generics)闭包(Closures)**来处理动态逻辑。

B. 性能开销

NSInvocation 的创建和执行涉及大量的动态查找和堆内存分配。Swift 追求静态绑定(Static Dispatch)和内联优化。即便需要动态性,Swift 也更倾向于使用**函数表(V-Table)**或 Witness Table,而不是慢速的消息转发。

C. 替代方案的崛起

Swift 提供了更现代、更优雅的工具来完成 NSInvocation 的工作:

  • 闭包 (Closures): 闭包可以捕获上下文和参数,实现“延迟执行”。
  • KeyPath: 提供了类型安全的属性访问。
  • Distributed Actors / Swift Macros: 解决了一些原本需要靠动态代理(Proxy)才能完成的高级功能。
  • 泛型编程: 解决了动态处理不同类型数据的问题。

3. 对比总结

特性NSInvocation (Obj-C)Swift 替代方案
本质运行时消息的“切片”对象闭包、泛型、函数类型
安全性极低(容易引发无效指针崩溃)极高(编译期类型检查)
动态性极高(可在运行时修改一切)适中(强调协议和静态多态)
性能慢(涉及多次内存拷贝)快(趋近于直接调用)

深入思考

虽然 Swift 官方没有提供 NSInvocation 的对应物,但在处理 Target-Action 模式或与 Obj-C 老库交互时,我们偶尔仍会怀念它的灵活性。不过,随着 Swift 反射机制(Reflection)和宏(Macros)的完善,这种需求正变得越来越少。