你可能在代码审查中听过“请给这个类加上 final”之类的话。
看起来只是习惯,但了解原因后,你看代码的方式会发生变化。
先说结论,final 表示“这个类不打算通过继承进行扩展”。这一行比想象中更有分量,能同时提升性能和设计稳定性。
今天就来总结为什么 Swift 类通常要加上 final,以及背后的真正原因。
final 到底是什么?
final 是用于阻止继承和重写的关键字。
放在类前面时,可以阻止其他类继承该类。
放在方法或属性前面时,也可以只阻止该成员被重写。
final class Logger {
func log(_ msg: String) { print("[LOG] \(msg)") }
}
let logger = Logger()
logger.log("支付完成") // 输出: [LOG] 支付完成
如果为了继承 Logger 而写成 class FileLogger: Logger,就会出现编译错误。
也就是把“这个类到此为止”这句话钉死。
加上 final 后为什么会更快?
第一个原因是性能。关键在于分派方式。
只要继承仍然开放,编译器就无法在运行时之前确定实际会调用哪个方法,因此每次都必须查表寻找。
这叫作动态分派。它会先查询每个类的方法表(vtable),再进行调用。
而加上 final 后,情况就不同了。
由于子类无法介入,编译器可以确定“这个调用一定对应这个方法”。
因此无需查表即可直接调用。这就是静态分派。
更进一步,较短的方法甚至可以进行内联,把代码直接展开到调用位置。
一次调用的成本并不高,但如果一个方法在循环中被调用数千次,差异就会累积起来。
真正的原因不是性能,而是“设计”
实际上,我认为这一点比性能更重要。
因为它可以避免脆弱基类问题(fragile base class)。
如果开放继承,任何人都可以自由介入你创建的父类的内部行为。
明明只是无意中修改了父类的一个方法,却可能让所有重写该方法的子类接连崩溃。
继承是最容易破坏封装的关系,因为子类可以看到父类的内部细节。
所以面向对象设计原则也会这样说:
为继承而设计并编写文档;否则就禁止继承。——这是《Effective Java》中的著名建议。
final 就是将这条建议落实到代码中的工具。
编译器会强制执行这样的意图:“既然没有按继承设计,就不要把它开放出去”。
意图明确后,之后阅读代码的同事也会少一些困惑。
那么是不是所有类都应该加上 final?
这里有一点很重要:在 Swift 中,引用类型不一定非要是类。
如果状态可以按值处理,struct 往往是更好的选择。因为 struct 从一开始就没有继承。
也就是说,在考虑“要不要加 final”之前,应该先问“这真的必须是一个类吗?”
如果仍然需要使用类,就按照下面的标准判断。
| 情况 | 判断 |
|---|---|
| 没有考虑继承的类 | 加上 final(默认) |
| 对性能敏感的重复调用代码 | 使用 final 引导静态分派 |
| 设计了明确扩展点的框架 | 不加 final,保持开放 |
| 以继承为前提的 API,例如 UIViewController | 不加 |
总结来说,记住下面几点就很方便。
- 如果没有继承意图,默认加上 final
- 只开放你确实想开放的扩展点
- 如果 struct 就能完成,从一开始就避免使用类
面试时会这样问
Q. 加上 final 关键字有什么好处?
它可以阻止继承和重写,清楚表达设计意图。同时,编译器可以使用静态分派代替动态分派,从而降低调用成本,并支持内联优化。
Q. 那么所有类都应该加上 final 吗?
如果类是为继承而设计并编写文档的,就应该保持开放。不过,没有这种意图的类默认关闭会更安全;如果值语义合适,也应该优先考虑 struct。
final 这一行不只是简单的优化技巧,更是在回答“应该如何使用这个类”。
从今天开始,每次创建新类时都问自己一次:“有什么理由要开放继承吗?”只要养成这个习惯,代码就会稳固得多。

