本文翻译自官方 swift 博客,感谢戴仓薯校稿。
和其他许多语言一样,Swift 允许一个类的子类重写方法和属性。这就意味着程序必须在运行时决定关联哪个方法或属性,从而间接调用或间接访问。这项叫动态派送的技术,以每次间接调用固定的运行时开销为代价,增强了语言的表达性。如果对于性能敏感,这种方法往往是不可取的。这篇文章将介绍3种消除动态机制来提升性能的方法:final
, private
和全局模块优化。
先细读下面例子:class ParticleModel {
var point = ( 0.0, 0.0 )
var velocity = 100.0
func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
var p = ParticleModel()
for i in stride(from: 0.0, through: 360, by: 1.0) {
p.update((i * sin(i), i), newV:i*1000)
}
如上所写,编译器会建立一个动态派送调用:
- 调用
update()
, - 调用
updatePoint()
, - Get 属性
point
元组, - Get 属性
velocity
。
当你当时看这段代码,这可能和你预想的不一样。因为 ParticleModel
的子类可能使用计算属性重写 point
或 velocity
,或者重写 updatePoint()
或 update()
方法,因此动态调用是必要的。
在 Swift,执行动态派送调用就是在方法列表里寻找函数,然后间接调用。这会慢于直接调用。另外,间接调用阻止了许多编译器优化,使得使用间接调用成本更高。在性能要求严格的情况下,这里有一些技术可以在不需要动态行为的时候提升性能。
当你确定声明不需要被重写时,使用 final
关键字 final
可以标示类、方法和属性的声明(declaration)不能被重写。编译器便会安全地省略动态派送。例如在下面这个代码,访问 point
和 velocity
会直接加载对象的储存属性,并且调用 updatePoint()
是直接调用。另一方面,update()
还是会通过动态派送调用,也允许子类重写 update()
。class ParticleModel {
final var point = ( x: 0.0, y: 0.0 )
final var velocity = 100.0
final func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
把整个类标记 final
也是可行的,只需加在 class 前面。这会禁止子类继承,隐式地声明该类所有的函数和属性都是 final
。final class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
通过应用 private 关键字,把仅在一个文件中出现的声明推断为 final
给声明(declaration)应用 private
关键字会把它限制为仅在当前文件可见。这允许编译器去寻找所有潜在可重写的声明。未存在重写的声明会使得编译器自动推断为 final
,去除访问方法和属性时的间接调用。
假设没有一个类在当前文件重写了 ParticleModel
,编译器会将所有 private 声明的动态派送调用替换为直接调用。class ParticleModel {
private var point = ( x: 0.0, y: 0.0 )
private var velocity = 100.0
private func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
上面这个例子,point
和 velocity
会被直接访问,updatePoint()
会被直接调用。而 update()
则再一次被间接调用,因没有 private 标记。
和 final
一样,给类声明标记 private
也是可以的,这回使得类是私有的,它的所有属性和方法也是私有的。private class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
使用全局模块优化,将 internal 声明推断为 final
带有 internal
访问的声明(如果没有显式声明,默认也是如此)只有在当前模块才可见。因为 Swift 通常会分开编译同一模块的不同文件,编译器不能推断一个 internal
声明会不会在其他文件中被重写。但是,如果全局模块优化可用,所有的模块会在同一时间被编译。这就允许编译器将所有模块一起处理,如果 internal
声明没有可见的重写,就推断为 final。
(译者注:全局模块优化设置位置在:Build Setting >> Swift Compiler-Code Generation >> Whole Module Optimization。)
回到最开始的代码,这次我们给 ParticleModel
加些 public
关键字。public class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
point = newPoint
velocity = newVelocity
}
public func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
var p = ParticleModel()
for i in stride(from: 0.0, through: times, by: 1.0) {
p.update((i * sin(i), i), newV:i*1000)
}
当用全局模块优化编译这段代码,编译器会推断属性 point,velocity
和方法 updatePoint()
为 final
。相对的,update()
被标记为 public,不会被推断为 final
。