和其它许多语言一样,Swift 允许子类覆写父类的方法和属性。这意味着程序需要在运行时才能确定哪个方法和属性被调用和访问。这便是通常称为的动态派发。动态派发增加了语言的可表达性,但是会牺牲恒定的性能时间。这在性能敏感的代码中时不可取的。
来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ) }
上面的代码,编译器将会执行动态派发,其流程是:
调用p的update方法
调用p的updatePoint方法
获取p的point属性
获取p的velocity属性
因为 ParticleModel 的 属性和方法可能会被其子类覆写,所以对 ParticalModel 的属性和方法的访问和调用都是动态派发的。
在 Swift 里,动态派发的实现是从函数表中查找相应的方法后间接调用。这会比直接调用要慢。另外,间接调用还会阻止许多编译器的优化使得间接调用更耗性能。所以,对于性能要求比较高的代码有几种技巧可以限制动态派发。
使用 final final 关键字可以用在类、方法和属性上,表明这些是不可以被覆写的。这可以让编译器安全地忽略动态派发过程。例如,在下面的代码中,point 和 velocity 将通过对象存储属性的加载直接访问,并通过直接函数调用来调用 updatePoint()。另一方面,update() 因为没有使用 final 关键字,可以被重写,所以将会继续使用动态派发。
1 2 3 4 5 6 7 8 9 10 11 12 13 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 关键字,这样的话这个类就无法再被子类化,其属性和方法自然也就不可能被重写,所以就不存在动态派发了。
自动推断为final 在声明中使用 private 关键字会将可见性限制为当前文件。这样编译器就能轻松地找到所有可以被重写的方法。无法被重写的方法和属性将会被编译器自动推断为使用 final,并删除对方法和属性的间接调用。 假设在当前文件里没有类重写 ParticleModel,编译器会替换所有的动态派发为直接调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 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) } }
使用 Whole Module Optimization 使用默认的权限控制只在声明的模块内可见。因为 Swift 通常是按照模块分别编译的,编译器无法确定一个 internal 的声明是否会在另一个模块中被重写。但是,如果开启了 Whole Module Optimization 的话,所有模块会在一起被编译,这样编译器便可以在编译时判断哪些声明是没有被重写的,因此可以推断出哪些可以使用 final 关键字。 还是之前的代码,这次为 ParticalModel 和 update 方法添加上 public 关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ) }
当使用 Whole Module Optimization 选项编译这段代码时,编译器可以推断出 point,velocity 和 updatePoint() 方法都可以使用 final 关键字。相反,update() 因为声明为 public,所以无法被优化为使用 final。