5-15.【性能分析与优化】哪些情况一定是动态派发?哪些一定是静态派发?

1 阅读2分钟

1️⃣ 静态派发(Static Dispatch)

特点

  • 编译期确定调用目标函数
  • 零开销(没有额外指针查找)
  • LLVM 可以进一步 内联、向量化、优化 CoW

一定是静态派发的情况

场景示例说明
普通函数/全局函数func foo() { ... }编译器知道函数地址
结构体/枚举的普通方法struct S { func bar() {} }值类型方法默认静态派发
泛型函数特化func add<T: Numeric>(_ a: T, _ b: T) -> T { a + b },T 已知泛型特化生成的具体函数,静态调用
final class 方法final class C { func baz() {} }final 修饰 → 不可被子类重写 → 编译器可静态调用
内联函数/编译期已知类型@inline(__always) func f() {}LLVM 可直接内联展开

总结

值类型方法、非 dynamic 的函数、final 类方法、特化泛型 → 静态派发


2️⃣ 动态派发(Dynamic Dispatch)

特点

  • 调用目标在运行期确定
  • 通过 vtable / witness table / objc_msgSend 间接调用
  • 有额外开销(CPU 多一次指针跳转)

一定是动态派发的情况

场景示例说明
非 final class 的普通方法class C { func foo() {} }子类可重写 → 通过 vtable 调用
@objc 方法@objc func bar() {}Objective-C runtime 动态派发 → objc_msgSend
协议存在类型方法(existential)protocol P { func p() } let x: P = ...; x.p()通过 witness table 动态调用
动态属性@objc dynamic var name: String每次访问都是动态派发
泛型函数在调用方类型未知func f<T>(_ x: T) { ... },T 未具体化无法特化,只能使用通用泛型代码 → 有动态 type metadata 调用

总结

类方法可被子类重写、@objc/dynamic 方法、协议存在类型方法、未特化泛型 → 动态派发


3️⃣ 派发方式对比表

特性静态派发动态派发
调用时机编译期确定运行期确定
性能高,直接调用低,间接调用(vtable / witness table / objc_msgSend)
可优化性可内联、向量化、消除 CoW内联受限,LLVM 难以优化
典型场景值类型方法、final class、泛型特化非 final class、协议存在类型、@objc/dynamic
可预测性完全可预测受运行时类型影响

4️⃣ 核心原则

  1. 值类型方法 → 静态派发
  2. final 修饰的 class 方法 → 静态派发
  3. 普通 class 方法 → 动态派发
  4. 协议存在类型(existential) → 动态派发
  5. 泛型函数特化 → 静态派发;未特化 → 通用泛型动态派发
  6. @objc / dynamic → 动态派发

总结一句话:可在编译期确定调用目标 → 静态派发;必须运行期判断类型 → 动态派发