理解 Swift 的方法派发机制,是性能优化和设计架构的基础。为什么 Struct 比 Class 快?答案就在派发方式。
1. 三种派发方式
Swift 中存在三种方法派发机制,各有优劣:
| 派发方式 | 时机 | 速度 | 灵活性 | 代表 |
|---|---|---|---|---|
| 静态派发 | 编译期 | 极快 | 低 | Struct, final |
| 函数表派发 | 运行时 | 快 | 中 | Class |
| 消息派发 | 运行时 | 慢 | 高 | @objc, dynamic |
2. 静态派发 (Static Dispatch)
编译器在编译时就确定要调用的函数地址,可以进行内联优化。
特点
- 编译期确定:无运行时开销
- 可内联优化:编译器可以将函数体直接插入调用处
- 无多态:不支持子类重写
使用场景
// 1. Struct 的方法
struct Point {
var x: Double
var y: Double
// 静态派发
func distance(to other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return sqrt(dx * dx + dy * dy)
}
}
// 2. final 修饰的 Class
final class FinalPerson {
var name: String
init(name: String) { self.name = name }
// 静态派发(因为是 final)
func greet() {
print("Hello, \(name)")
}
}
// 3. private/fileprivate 方法
class MyClass {
// 编译器可以推断为静态派发
private func internalWork() { }
}
// 4. Extension 中的方法
extension String {
// 静态派发
func reversed() -> String {
String(self.reversed())
}
}
3. 函数表派发 (V-Table Dispatch)
通过虚函数表在运行时查找要调用的方法,支持多态。
工作原理
┌─────────────────────────────┐
│ Class 实例 │
├─────────────────────────────┤
│ isa 指针 → 类元信息 │
│ 存储属性... │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ 类元信息 │
├─────────────────────────────┤
│ V-Table: │
│ [0] → method1 地址 │
│ [1] → method2 地址 │
│ [2] → method3 地址 │
└─────────────────────────────┘
示例
class Animal {
func speak() {
print("...")
}
}
class Dog: Animal {
override func speak() {
print("汪汪!")
}
}
class Cat: Animal {
override func speak() {
print("喵喵~")
}
}
// 运行时根据实际类型查找 V-Table
let animals: [Animal] = [Dog(), Cat(), Dog()]
for animal in animals {
animal.speak() // V-Table 派发
}
// 输出: 汪汪! 喵喵~ 汪汪!
V-Table 布局
class Parent {
func method1() { } // V-Table[0]
func method2() { } // V-Table[1]
}
class Child: Parent {
override func method1() { } // 覆盖 V-Table[0]
func method3() { } // V-Table[2]
}
4. 消息派发 (Message Dispatch)
Objective-C 的派发方式,通过 objc_msgSend 在运行时动态查找方法。
特点
- 完全动态:可以在运行时添加、替换方法
- 支持 Method Swizzling
- 开销较大:需要遍历方法列表
使用场景
class MyViewController: UIViewController {
// @objc 使用消息派发
@objc func buttonTapped(_ sender: UIButton) {
print("按钮被点击")
}
// dynamic 强制使用消息派发
@objc dynamic func dynamicMethod() {
print("动态方法")
}
}
// Selector 需要 @objc
let selector = #selector(MyViewController.buttonTapped)
Method Swizzling
extension UIViewController {
static let swizzleViewDidLoad: Void = {
let originalSelector = #selector(viewDidLoad)
let swizzledSelector = #selector(swizzled_viewDidLoad)
guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),
let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector)
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}()
@objc func swizzled_viewDidLoad() {
swizzled_viewDidLoad() // 调用原方法
print("视图已加载: \(type(of: self))")
}
}
5. 派发规则总结
默认派发方式
| 声明位置 | 类型 | 默认派发 |
|---|---|---|
| 初始声明 | Struct/Enum | 静态 |
| 初始声明 | Class | V-Table |
| 初始声明 | Protocol | Witness Table |
| Extension | 值类型 | 静态 |
| Extension | Class | 静态 |
| Extension | Protocol | 静态(默认实现) |
修饰符影响
class MyClass {
func normalMethod() { } // V-Table
final func finalMethod() { } // 静态
@objc func objcMethod() { } // 消息派发
@objc dynamic func dynamicMethod() { } // 消息派发
}
extension MyClass {
func extensionMethod() { } // 静态(无法 override)
@objc func objcExtMethod() { } // 消息派发
}
6. Protocol Witness Table
协议使用 Witness Table 实现多态:
protocol Drawable {
func draw()
}
struct Circle: Drawable {
func draw() { print("画圆") }
}
struct Square: Drawable {
func draw() { print("画方") }
}
// 使用 Witness Table 派发
func render(_ drawable: Drawable) {
drawable.draw()
}
render(Circle()) // 画圆
render(Square()) // 画方
存在容器 (Existential Container)
┌─────────────────────────────────┐
│ Existential Container │
├─────────────────────────────────┤
│ Value Buffer (24 bytes) │ ← 存储值或指针
│ Value Witness Table 指针 │ ← 内存管理
│ Protocol Witness Table 指针 │ ← 方法查找
└─────────────────────────────────┘
7. 性能优化建议
使用 final 提升性能
// ✅ 不需要被继承的类标记 final
final class DataManager {
func process() { } // 静态派发
}
// ✅ 不需要被重写的方法标记 final
class ViewController: UIViewController {
final func setupUI() { } // 静态派发
}
使用 private 让编译器优化
class MyClass {
// 编译器可以推断为 final,使用静态派发
private func internalMethod() { }
// fileprivate 同理
fileprivate func fileMethod() { }
}
Whole Module Optimization
在 Build Settings 中启用:
- Optimization Level:
-O或-Osize - Whole Module Optimization: Yes
编译器会分析整个模块,将未被继承的类自动标记为 final。
8. 实际性能对比
// 测试代码
let iterations = 10_000_000
// Struct (静态派发) - 最快
struct StructCalculator {
func add(_ a: Int, _ b: Int) -> Int { a + b }
}
// Final Class (静态派发) - 接近 Struct
final class FinalClassCalculator {
func add(_ a: Int, _ b: Int) -> Int { a + b }
}
// Class (V-Table 派发) - 略慢
class ClassCalculator {
func add(_ a: Int, _ b: Int) -> Int { a + b }
}
// @objc dynamic (消息派发) - 最慢
class ObjCCalculator: NSObject {
@objc dynamic func add(_ a: Int, _ b: Int) -> Int { a + b }
}
性能排序
静态派发 (Struct/final) > V-Table 派发 (Class) > 消息派发 (@objc dynamic)
~1x ~1.2x ~4x
9. 最佳实践
✅ 推荐
// 1. 优先使用 Struct
struct User {
let id: UUID
var name: String
}
// 2. 不需要继承的类用 final
final class APIClient { }
// 3. 使用 private 限制访问
class ViewModel {
private func internalLogic() { }
}
// 4. 只在必要时使用 @objc
class MyView: UIView {
@objc func handleTap() { } // 需要 Selector
}
❌ 避免
// 1. 不要过度使用 @objc dynamic
class Calculator {
@objc dynamic func add() { } // ❌ 不必要的性能损失
}
// 2. 不要在 Extension 中期望 override
extension MyClass {
func extensionMethod() { } // ❌ 不能被子类 override
}