消息分发(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 dynamic
protocol 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 后,这种优化会更加激进。