方法派发机制

底层硬核原理:对比静态派发、虚函数表派发与消息转发在运行时对 App 性能的影响。

理解 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
}