基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Swift final 关键字的作用及其带来的性能优化?

知识点图片

在 Swift 中,final 是一个修饰符(Modifier),主要用于类(Class)及其成员(属性、方法、下标)

它的作用可以从两个维度来理解:语义层面(功能限制)编译器层面(性能优化)


一、 语义层面:功能限制

final 的核心含义是“最终的”、“不可更改的”。它限制了面向对象编程中的继承和重写特性。

1. 修饰类(final class

当一个类被声明为 final 时,它不能被继承

  • 这意味着这个类是继承树中的“叶子节点”。
plaintext
final class Box {
    var name: String = ""
}

// ❌ 编译报错:Inheritance from a final class 'Box'
class GiftBox: Box { }

2. 修饰类成员(final var, final func

当类中的属性或方法被声明为 final 时,子类不能重写(Override)这些成员,但子类依然可以继承并使用它们。

plaintext
class Animal {
    final func eat() {
        print("Eating...")
    }
    
    func sleep() {
        print("Sleeping...")
    }
}

class Cat: Animal {
    // ✅ 合法:可以直接调用 eat()
    
    // ❌ 编译报错:Instance method overrides a 'final' instance method
    override func eat() { 
        print("Eating fish")
    }
    
    // ✅ 合法:sleep 没有被标记为 final
    override func sleep() {
        print("Cat sleeping")
    }
}

二、 编译器层面:性能优化

这是面试中常考的重点。final 带来的性能提升主要源于它改变了 Swift 的方法派发机制(Method Dispatch)

1. 动态派发 vs. 静态派发

  • 默认情况(动态派发 / Dynamic Dispatch):
    在 Swift 中,类(Class)是引用类型。为了支持多态(Polymorphism),默认情况下,调用一个对象的方法时,编译器无法在编译阶段确定具体执行哪个方法(因为该变量可能指向子类实例,而子类可能重写了该方法)。
    因此,运行时需要通过虚函数表(V-Table)进行查找,找到对应的方法内存地址后再跳转执行。这虽然灵活,但有少量的运行时开销(查找表 + 无法进行编译器优化)。

  • 使用 final 后(静态派发 / Static Dispatch):
    当你将类或方法标记为 final,你明确告诉编译器:“这个方法永远不会被重写”。
    编译器既然知道了这一点,就不需要再去查虚函数表了。它可以在编译阶段直接生成调用该方法具体内存地址的指令。这种机制称为去虚化(Devirtualization)直接派发(Direct Dispatch)

2. 函数内联(Function Inlining)

静态派发带来的最大好处不仅仅是省去了查表的时间,更重要的是它允许编译器进行函数内联

  • 什么是内联?
    如果一个 final 方法体很短(例如只是返回一个属性值),编译器会将该方法的代码直接“复制粘贴”到调用它的地方,完全省去了函数调用(压栈、跳转、出栈)的开销。

性能对比示例:

plaintext
class Calculator {
    func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
}

// 调用
let calc = Calculator()
let result = calc.add(1, 2)
  • 不加 final 运行时 \rightarrow 查找 calc 实例的 V-Table \rightarrow 找到 add 函数地址 \rightarrow 跳转执行 \rightarrow 返回。
  • 加了 final 编译器发现 add 很简单且不可重写 \rightarrow 直接将 let result = calc.add(1, 2) 优化为机器码层面的 let result = 1 + 2

三、 总结与最佳实践

总结表

特性 普通 Class / 方法 final Class / 方法
继承/重写 允许 禁止
派发机制 动态派发 (V-Table) 静态派发 (Direct Dispatch)
性能 较慢(有运行时查找开销) 较快(直接跳转,可能内联)
编译器优化 限制较多 允许深度优化(如内联)

什么时候使用 final

  1. 类设计明确不希望被继承时: 例如单例类、工具类(Helper/Utils),或者你编写的库中不希望使用者修改逻辑的核心类。
  2. 追求极致性能时: 在高频调用的循环中,如果方法不需要多态,加上 final 可以减少 CPU 开销。
  3. Swift 编译器的隐式优化: 值得注意的是,如果一个方法是 private 的,编译器通常会自动将其推断为 final 进行优化,因为外部无法访问也就无法重写。

一句话总结:
final 关键字通过禁止继承和重写,将方法的动态派发转换为静态派发,从而允许编译器进行去虚化和内联优化,提升程序运行效率。

00:00
00:00