闽公网安备 35020302035485号
消息分发(Message Dispatch) - 最慢
// 1. 结构体的方法(没有继承,所以确定)
struct Dog {
func bark() {
print("汪汪汪")
}
}
// 2. final 类或方法(不能被重写)
finalclass Cat {
func meow() {
print("喵喵喵")
}
}
class Animal {
finalfunc breathe() {
print("呼吸中...")
}
}
// 3. private 方法(外部看不到,不能重写)
class Bird {
privatefunc fly() {
print("飞行中...")
}
}
// 4. 协议扩展中的方法(默认实现)
protocol Walkable {
func walk()
}
extension Walkable {
func walk() { // 这个是静态分发
print("走路中...")
}
}
我之前就踩过一个坑。写了个协议,然后在扩展里提供了默认实现,以为子类重写后就能生效。结果发现通过协议类型调用时,永远走的是扩展里的实现。后来才知道,协议扩展中的方法用的是静态分发!class Vehicle {
func startEngine() {
print("Vehicle engine started")
}
}
class Car: Vehicle {
overridefunc startEngine() {
print("Car engine started")
}
}
class Truck: Vehicle {
overridefunc startEngine() {
print("Truck engine started")
}
}
let vehicles: [Vehicle] = [Car(), Truck()]
for vehicle in vehicles {
// 这里用的是动态分发
// 运行时查询每个对象的虚表,决定调用哪个实现
vehicle.startEngine()
}
虽然动态分发比静态分发慢一些,但也只是多了两个步骤:读取虚表 + 跳转。对于现代处理器来说,这点开销其实可以忽略不计。class LegacySystem: NSObject {
@objc dynamic func processData() {
print("Processing data in legacy system")
}
}
// 可以在运行时修改方法实现(Method Swizzling)
// 这在静态分发和动态分发中都是不可能的
消息分发的查找过程比较复杂:先从当前类开始查找,找不到就往父类找,一直找到 NSObject 为止。好在有缓存机制,所以实际性能影响没有想象中那么大。// 堆代码 duidaima.com
final class NetworkManager {
final func request() {
// 这个方法用静态分发,性能最优
}
}
2. 避免不必要的 @objc dynamicprotocol Drawable {
func draw()// 这是协议要求,用动态分发
}
extension Drawable {
func draw() { // 这是默认实现,用静态分发
print("Drawing...")
}
func erase() { // 这也是静态分发
print("Erasing...")
}
}
struct Circle: Drawable {
func draw() { // 重写协议要求
print("Drawing circle")
}
func erase() { // 这个重写是"无效"的!
print("Erasing circle")
}
}
let shape: Drawable = Circle()
shape.draw() // 输出 "Drawing circle"(动态分发)
shape.erase() // 输出 "Erasing..."(静态分发,走的是扩展实现)
这个例子告诉我们:协议中声明的方法用动态分发,扩展中的方法用静态分发。class Animal {
func makeSound() {
print("Some animal sound")
}
}
let dog = Animal() // 编译器知道这肯定是 Animal 类型
dog.makeSound() // 可能被优化成静态分发
即使 makeSound 理论上应该用动态分发,但编译器知道 dog 肯定是 Animal 类型,所以可能直接优化成静态调用。开启 Whole Module Optimization 后,这种优化会更加激进。