和其他許多語言一樣,Swift 允許一個類的子類重寫方法和屬性。這就意味著程式必須在執行時決定關聯哪個方法或屬性,從而間接呼叫或間接訪問。這項叫動態派送的技術,以每次間接呼叫固定的執行時開銷為代價,增強了語言的表達性。如果對於效能敏感,這種方法往往是不可取的。這篇文章將介紹3種消除動態機制來提升效能的方法:final
, private
和全域性模組優化。
先細讀下面例子:
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) } |
如上所寫,編譯器會建立一個動態派送呼叫:
- 呼叫
update()
, - 呼叫
updatePoint()
, - Get 屬性
point
元組, - Get 屬性
velocity
。
當你看這段程式碼時,這可能和你預想的不一樣。因為 ParticleModel
的子類可能使用計算屬性重寫 point
或 velocity
,或者重寫 updatePoint()
或 update()
方法,因此動態呼叫是必要的。
在 Swift,執行動態派送呼叫就是在方法列表裡尋找函式,然後間接呼叫。這會慢於直接呼叫。另外,間接呼叫阻止了許多編譯器優化,使得使用間接呼叫成本更高。在效能要求嚴格的情況下,這裡有一些技術可以在不需要動態行為的時候提升效能。
當你確定宣告不需要被重寫時,使用 final
關鍵字 final
可以標示類、方法和屬性的宣告(declaration)不能被重寫。編譯器便會安全地省略動態派送。例如在下面這個程式碼,訪問 point
和 velocity
會直接載入物件的儲存屬性,並且呼叫 updatePoint()
是直接呼叫。另一方面,update()
還是會通過動態派送呼叫,也允許子類重寫 update()
。
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
也是可行的,只需加在 class 前面。這會禁止子類繼承,隱式地宣告該類所有的函式和屬性都是 final
。
1 2 3 4 5 |
final class ParticleModel { var point = ( x: 0.0, y: 0.0 ) var velocity = 100.0 // ... } |
通過應用 private 關鍵字,把僅在一個檔案中出現的宣告推斷為 final
給宣告(declaration)應用 private
關鍵字會把它限制為僅在當前檔案可見。這允許編譯器去尋找所有潛在可重寫的宣告。未存在重寫的宣告會使得編譯器自動推斷為 final
,去除訪問方法和屬性時的間接呼叫。
假設沒有一個類在當前檔案重寫了 ParticleModel
,編譯器會將所有 private 宣告的動態派送呼叫替換為直接呼叫。
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) } } |
上面這個例子,point
和 velocity
會被直接訪問,updatePoint()
會被直接呼叫。而 update()
則再一次被間接呼叫,因沒有 private 標記。
和 final
一樣,給類宣告標記 private
也是可以的,這回使得類是私有的,它的所有屬性和方法也是私有的。
1 2 3 4 5 |
private class ParticleModel { var point = ( x: 0.0, y: 0.0 ) var velocity = 100.0 // ... } |
使用全域性模組優化,將 internal 宣告推斷為 final
帶有 internal
訪問的宣告(如果沒有顯式宣告,預設也是如此)只有在當前模組才可見。因為 Swift 通常會分開編譯同一模組的不同檔案,編譯器不能推斷一個 internal
宣告會不會在其他檔案中被重寫。但是,如果全域性模組優化可用,所有的模組會在同一時間被編譯。這就允許編譯器將所有模組一起處理,如果 internal
宣告沒有可見的重寫,就推斷為 final。
回到最開始的程式碼,這次我們給 ParticleModel
加些 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) } |
當用全域性模組優化編譯這段程式碼,編譯器會推斷屬性 point,velocity
和方法 updatePoint()
為 final
。相對的,update()
被標記為 public,不會被推斷為 final
。