原文鏈接:Regarding Swift build time optimizations
上周站玄,在我讀完 @nickoneill 寫的一篇優(yōu)秀的博文《為緩慢的Swift編譯時間提速》后,我發(fā)現用一個不同的角度去審視 Swift 代碼并不是很難的一件事介袜。
可以被認為是簡潔的一行代碼現在引發(fā)了一個新的問題 -- 是否應該把這行代碼重構成對應的9行代碼以讓編譯器更容易工作(看看接下來要講的關于空合運算符(nil coalescing operator)的示例)仪芒?到底哪個才是更重要的唁影,簡潔的代碼還是對編譯器友好的代碼耕陷?這取決于項目的大小和開發(fā)者的想法。
慢著据沈。哟沫。。這里有一個 Xcode 插件
在展示具體的例子之前卓舵,我先想到就是手動查看日志是一件非常耗時的事情南用。有人提出了用終端命令可以讓這件事情變得比較容易,但是我更進一步掏湾,把這個用 Xcode 插件 給實現出來了裹虫。
對我來說,最初的目的就是找到并修復最耗時的地方融击,但我現在的想法是讓它經歷更多的迭代過程筑公。這樣的話我就不僅可以讓代碼編譯更有效率,還可以防止第一次進入項目的耗時尊浪。
更加驚喜的是
我經常在多個 Git 分支間跳來跳去匣屡,等待一個緩慢的項目編譯完成往往浪費了大量的時間。我想了好長一段時間為什么我的一個寵物項目會編譯地這么緩慢(大概2萬行 Swift 代碼)拇涤。
在我學習了究竟是原因導致的這件事之后捣作,我不得不承認我的確很吃驚,一行代碼就需要幾秒鐘來編譯鹅士。
讓我們一起看看幾個例子券躁。
空合操作符
編譯器是很不喜歡這里的第一種方式的。在展開下面兩處簡寫的代碼之后掉盅,編譯時間減少了99.4%也拜。
// 編譯時間: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
// 編譯時間: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
padding += rightView.bounds.width
}
if let leftView = leftView {
padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)
ArrayOfStuff + [Stuff]
這個看起來像下面這樣:
return ArrayOfStuff + [Stuff]
// 而不是
ArrayOfStuff.append(stuff)
return ArrayOfStuff
我經常這樣做,每次都會對所需的編譯時間產生影響趾痘。下面是最差的一個慢哈,這里的編譯時間減少了97.9%。
// 編譯時間: 1250.3ms
let systemOptions = [ 7, 14, 30, -1 ]
let systemNames = (0...2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")]
// 一些中間的代碼
labelNames = Array(systemNames[0..<count]) + [systemNames.last!]
// 編譯時間: 25.5ms
let systemOptions = [ 7, 14, 30, -1 ]
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat, $0) }
systemNames.append(NSLocalizedString("everything", comment: ""))
// 一些中間的代碼
labelNames = Array(systemNames[0..<count])
labelNames.append(systemNames.last!)
三元運算符
僅僅只是把三元運算符替換成 if-else 語句永票,就讓編譯時間減少了92.9%卵贱。如果將 map 換成 for 循環(huán),就又能減少75%(但是那樣的話我的眼睛可就受不了了)瓦侮。??
// 編譯時間: 239.0ms
let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)}
// 編譯時間: 16.9ms
var labelNames: [String]
if type == 0 {
labelNames = (1...5).map{type0ToString($0)}
} else {
labelNames = (0...2).map{type1ToString($0)}
}
轉換 CGFloat 到 CGFloat
沒聽懂我在說什么艰赞?其實下面例子中值已經是 CGFloat 了,并且有些括號是多余的肚吏。在清理完這些冗余之后方妖,編譯時間減少了99.9%。
// 編譯時間: 3431.7 ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180
// 編譯時間: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
Round()
下面是一個很奇怪的例子罚攀,下面的例子中變量是一個局部變量與實例變量的混合党觅。這個問題似乎不是出在四舍五入本身雌澄,而是在于結合代碼的方法。去掉四舍五入的方法大概能減少 97.6% 的構建時間杯瞻。
// 編譯時間: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// 編譯時間: 34.7ms
let expansion = a — b — c + d * 0.66 + e
注意:以上所有測試都在MacBool Air(13英寸镐牺,2013年中)上進行。
嘗試一下吧
不管你是否面臨過編譯時間太長的問題魁莉,編寫對編譯器友好的代碼都是非常有用的睬涧。我確信你會在其中找到一些驚喜。作為參考旗唁,這里有完整的代碼畦浓,我的工程中可以5秒內完成編譯…
import UIKit
class CMExpandingTextField: UITextField {
func textFieldEditingChanged() {
invalidateIntrinsicContentSize()
}
override func intrinsicContentSize() -> CGSize {
if isFirstResponder(), let text = text {
let size = text.sizeWithAttributes(typingAttributes)
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
}
return super.intrinsicContentSize()
}
}