Swift 确实有缓存优化,但它的核心机制与 Objective-C 的动态哈希表查找完全不同。
Swift 的设计哲学是**“尽可能静态化”**。为了追求极致性能,它将大部分工作从“运行时”挪到了“编译时”。
1. Swift 的三种派发机制 (Dispatch)
要聊 Swift 的缓存,必须先看它的三种派发方式。Swift 没有像 OC 那样“一把梭”全走 objc_msgSend,而是根据场景选择了不同的路径:
| 派发方式 | 对应场景 | 性能 | 机制 |
|---|---|---|---|
| 直接派发 (Static) | struct、final class、扩展中的方法 | 极快 (类似 C++) | 编译时确定地址,支持内联 (Inline) |
| 函数表派发 (Table) | class 中的普通方法 | 快 | 使用 vtable 或 witness table |
| 消息派发 (Message) | 带有 @objc dynamic 的方法 | 较慢 | 走 OC Runtime (objc_msgSend) |
2. Swift 的“缓存”机制:vtable
在 Swift 类中,类似于 OC Method Cache 的机制是 vtable (Virtual Method Table) 。
它和 OC Cache 的区别:
- OC Cache (哈希表) :动态生成的。第一次调用时没有,找完后填进去。它是按需加载的。
- Swift vtable (数组) :在编译时就固定好了。每一个
class在内存中都有一个连续的数组,里面存好了所有方法的地址偏移量。
为什么 Swift 理论上更快?
-
OC:需要计算哈希值、处理哈希冲突、从内存读取
cache_t。 -
Swift:只需要“基地址 + 偏移量”即可命中。
MOV X0, [X1, #0x10](伪代码:直接从偏移 16 字节处取地址并跳转)。- 这种方式对 CPU 的分支预测极其友好。
3. 核心差异对比:Swift vs OC
① 动态性 vs 静态性
- OC:支持运行时交换方法(Swizzling)。因为它是查哈希表,你换了表里的内容,调用就变了。
- Swift (vtable) :默认不支持 Swizzling。偏移量在编译后就“写死”在二进制里了。
② 协议的缓存:Witness Table
Swift 的协议(Protocol)使用 PWT (Protocol Witness Table) 。
当一个泛型函数接收一个协议类型时,Swift 会带上一个隐藏的指针指向 PWT。这比 OC 的 respondsToSelector: 加 performSelector: 要安全且高效得多。
③ 缓存失效
- OC:扩容、Swizzling 或类结构改变都会导致
cache_t整个清空。 - Swift:由于是静态确定的表,不存在缓存失效的情况。
4. 特殊情况:Swift 中的“影子”缓存
虽然 Swift 主打静态,但在以下两种情况它会表现得像 OC:
NSObject子类:如果你继承自NSObject且没有标记final,Swift 依然可能为了兼容性生成一层包装,在内部调用objc_msgSend,从而利用 OC 的 Method Cache。- Devirtualization (去虚拟化) :Swift 编译器(SIL 优化器)非常聪明。如果它在编译时发现某个方法虽然写在
class里但从未被子类重写,它会直接把这个方法“降级”为直接派发,直接消除所有查找开销。
5. 总结:性能的代价
- Objective-C 牺牲了一点点初始性能,换取了极其强大的灵活性(如运行时热修复、Aspects 等)。
- Swift 牺牲了灵活性,换取了确定的、接近底层的执行效率。
比喻:
OC 是一个高级餐厅:你坐下点菜(发送消息),服务员去厨房问(查缓存),如果没有就看菜谱(找方法列表),最后把菜端上来。
Swift 是一个工厂流水线:每个工位(方法)的位置在建厂时就定死了,零件顺着传送带(vtable 偏移量)直接过去。
下一步建议:
如果你在做 Swift 性能优化,最有效的手段是大量使用 final 和 private 关键字。这能直接让编译器放弃 vtable 查找,转而使用直接派发。