上週,在我讀完 @nickoneill 寫的一篇優秀的博文《為緩慢的Swift編譯時間提速》後,我發現用一個不同的角度去審視 Swift 程式碼並不是很難的一件事。
可以被認為是簡潔的一行程式碼現在引發了一個新的問題 -- 是否應該把這行程式碼重構成對應的9行程式碼以讓編譯器更容易工作(看看接下來要講的關於空合運算子(nil coalescing operator)的示例)?到底哪個才是更重要的,簡潔的程式碼還是對編譯器友好的程式碼?這取決於專案的大小和開發者的想法。
慢著。。。這裡有一個 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 迴圈,就又能減少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()
}
}
複製程式碼