在 Swift 中,静态派发(Static Dispatch)与动态派发(Dynamic Dispatch)是方法调用的两种机制,对编写高性能代码至关重要。
一、静态派发
定义:在编译时确定调用哪个方法,直接通过内存地址跳转执行,无需运行时查找。但是用静态派发的方法(类)不可重写(继承)。
使用场景:
final
定义类或方法。struct
和enum
的方法默认静态派发。- 全局函数或
static
方法。
final 类示例:
final class Calculator {
func add(a: Int, b: Int) -> Int { a + b } // 静态派发
}
let result = Calculator().add(a: 1, b: 2) // 编译时直接确定调用地址
使用 @inline
内联方法,可以将静态派发优化到机制。
二、动态派发
定义:在运行时通过虚函数表(VTable)查找方法实现,支持继承和方法重写。灵活性更高,支持多态,但是存在性能开销(查表开销)。
使用场景:
- 非
final
类或方法。 @objc
标记的方法(兼容 OC 的动态性)。- 协议方法(除非用
extension
提供默认实现且未重写)。
动态派发示例:
class Animal {
func makeSound() { print("Generic sound") } // 动态派发
}
class Dog: Animal {
override func makeSound() { print("Bark") } // 运行时决定调用哪个实现
}
let animal: Animal = Dog()
animal.makeSound() // 输出 "Bark"(运行时动态查找)
三、常见问题
- 静态派发和动态派发的区别是什么:
- 静态派发在编译时确定方法地址,直接调用,速度快。
- 动态派发在运行时通过查表决定时机调用的方法,支持多态但行程稍差。
final
、struct
默认静态派发,普通类方法和协议通常是动态派发。
- 如何优化方法调用的性能:
- 对不需要继承的类或方法标记
final
启用静态派发。 - 优先使用
struct
。 - 避免滥用
@objc
和动态特性。
- 对不需要继承的类或方法标记
- 为什么 Swift 的协议方法有时是静态派发:
- 如果协议方法在
extension
中提供默认实现,且未被具体类型重写,编译器会优化为静态派发。 - 若被重写或协议方法声明在主体中,则 动态派发。
- Swift 协议的动态派发通过协议见证表(PWT)实现,不同于类的 VTable。
- 如果协议方法在
final
关键字除了性能优化还有什么作用:- 防止子类以外重写关键逻辑,提升安全性。
- 明确类设计意图。
- 动态派发在底层如何实现:
- Swift 的类通过虚函数表(VTable)存储方法地址,动态派发时按偏移量查表。
- OC 的运行时使用消息传递(
objc_msgSend
),灵活性更高但开销更大。