swift 作為一門新語言,受到了廣大開發(fā)者的喜愛,蘋果也極力在推swift,甚至最終會(huì)替換掉OC,筆者現(xiàn)在公司的項(xiàng)目也采用了swift編寫(三方庫除外),但發(fā)現(xiàn)一個(gè)很嚴(yán)重的問題就是編譯速度實(shí)在是難以讓人接受,因此筆者在如何提高swift的編譯速度方面做了相關(guān)調(diào)研,并將所學(xué)到的應(yīng)用到項(xiàng)目中,項(xiàng)目的編譯速度得到了很大的提升。文章將從兩方面來介紹如何提高swift項(xiàng)目的編譯速度,一是從代碼優(yōu)化上,一是從編譯器設(shè)置上。
前言#
在改善項(xiàng)目的編譯速度前,有必要知道到底是哪些函數(shù)編譯耗時(shí),哪些文件編譯耗時(shí),Robert 一個(gè)swift愛好者為我們提供了一個(gè)統(tǒng)計(jì)函數(shù)編譯時(shí)間的工具https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode,利用該工具能很方便的查出編譯耗時(shí)的地方。
一代碼層面優(yōu)化#
1.盡量避免類型推斷,能確定類型的一定要給出具體類型
func test1() {
let number = 32
let string = ""
let label = UILabel()
let dict = ["string1":"string","number":10,"label":label] as [String : Any]
var strings: [String] = []
}
func test2() {
let number:Int = 32
let string:String = ""
let label:UILabel = UILabel()
let dict:[String:Any] = ["string1":"string","number":10,"label":label] as [String : Any]
var strings: [String] = [String]()
}
test1采用了類型推斷耗時(shí)37.3ms,test2采用了精確的類型定義耗時(shí)10.5ms,減少了近三倍多的編譯時(shí)間。
2.nil類型問題
由于swift存在可選值,因此某些對(duì)象的值可能為空,這在代碼處理時(shí)可能會(huì)導(dǎo)致編譯很慢
func test3() ->Int {
var number1:Int?
var number2:Int?
var number3:Int?
return 10 + (number1 ?? 0) + (number2 ?? 0) + (number3 ?? 0)
}
func test4() ->Int {
var total = 10
var number1:Int?
var number2:Int?
var number3:Int?
if let number1 = number1 {
total = total + number1
}
if let number2 = number2 {
total = total + number2
}
if let number3 = number3 {
total = total + number3
}
return total
}
test3中number1,2,3可能存在nil,因此在返回時(shí)如果為nil,則給了默認(rèn)值0,結(jié)果編譯時(shí)間為7841.3ms,將近8s,太不可思議。而test4中對(duì)于可能為nil的情況下進(jìn)行了可選值綁定來判斷是否為nil,編譯時(shí)間為1.7ms,編譯時(shí)間跟test3不在一個(gè)量級(jí)上面,因此對(duì)于可能為nil的情況下,建議采用可選值綁定的方式來判斷,避免采用三的處理方式。
3.+ +=運(yùn)算
直接看代碼
func test5() {
var arrays = [Int]()
let arr1 = [1,2,3]
let arr2 = [3,4,5]
arrays += arr1 + arr2 + [10]
}
func test6() {
var arrays:[Int] = [Int]()
let arr1 = [1,2,3]
let arr2 = [3,4,5]
arrays.append(contentsOf: arr1)
arrays.append(contentsOf: arr2)
arrays.append(contentsOf: [10])
}
test5采用+ 將數(shù)組進(jìn)行合并耗時(shí)140.9ms,而test5采用系統(tǒng)提供的api進(jìn)行合并耗時(shí)2.3ms,因此對(duì)于數(shù)組合并的情況建議采用test6的形式。
4.復(fù)雜表達(dá)式計(jì)算
直接看代碼
func test7(string1:String,string2:String) {
let string = string1 + "你好" + string2 + "\(10)"
}
func test8(string1:String,string2:String) {
var string = string1
string = string + "你好"
string = string + string2
string = string + "\(10)"
}
test7表達(dá)式雖清晰,但復(fù)雜,編譯耗時(shí)23.4ms,test8將test7的表達(dá)式拆成幾部分,編譯時(shí)間1.3ms,表達(dá)式越簡(jiǎn)單,編譯時(shí)間越短,因此是編寫簡(jiǎn)潔明了的表達(dá)式,還是編寫對(duì)編譯器友好的表達(dá)式,我們是需要權(quán)衡的。
5.函數(shù)放在extension中,比不放在extension中編譯更耗時(shí),使用閉包也比較耗時(shí)。
二 編譯器層面優(yōu)化編譯時(shí)間#
- WHO
簡(jiǎn)單地說,Whole-Module Optimization(全模塊優(yōu)化,以下簡(jiǎn)稱 WMO),即在編譯項(xiàng)目時(shí),將同屬于一個(gè) Module(可以理解為一個(gè) Target、一個(gè) Package)的所有源代碼都串起來,進(jìn)行整體的一個(gè)分析與優(yōu)化,區(qū)別于 Single-File Optimization(單文件優(yōu)化,以下簡(jiǎn)稱 SFO),WMO 可以更好的統(tǒng)籌全局,去 inline 函數(shù)調(diào)用、排除死函數(shù)(即寫了卻從不調(diào)用的函數(shù))等等,使編譯速度加快。但問題來了,WMO 只是在 Release 模式下成為了默認(rèn)且推薦的選項(xiàng),在 Debug 模式下默認(rèn)依然是 None。
2.利用Uber團(tuán)隊(duì)在利用swift3重寫客戶端中發(fā)現(xiàn)的黑科技
Uber 的開發(fā)團(tuán)隊(duì)偶然發(fā)現(xiàn)如果把所有 Model 文件全部合并到一個(gè)文件去編譯, 那編譯時(shí)間會(huì)從 1min 35s 減少到 17s, 那么我們?nèi)绻阉写a文件都合并到一起, 那就可以極大地優(yōu)化編譯速度了。
WHO(Whole-Module-Optimization) 也會(huì)把文件合并起來再進(jìn)行編譯, 實(shí)際使用時(shí)我們發(fā)現(xiàn)編譯雖然快了, 但對(duì)于編譯時(shí)間的減少還是遠(yuǎn)沒有直接把文件合并到一起那么有效. 主要原因是因?yàn)?WHO 除了合并文件之外, 還會(huì)在預(yù)編譯階段做這些事情: 檢測(cè)沒有被調(diào)用的方法和類型, 在預(yù)編譯期去掉它們,給沒有被繼承的類, 沒有被繼承的方法加上 final 標(biāo)簽, 給編譯器提供更多信息, 以便這些方法被優(yōu)化為靜態(tài)調(diào)用或者是內(nèi)聯(lián)進(jìn)去,這些優(yōu)化會(huì)對(duì)于程序的效率有很大的提升, 但編譯時(shí)間會(huì)有所增加。
Uber 的團(tuán)隊(duì)發(fā)現(xiàn)通過增加一個(gè)編譯宏就可以做到只合并文件, 而不做優(yōu)化. 進(jìn)入工程文件設(shè)置 -> Build Setting -> Add User-Defined Settings, key 為 SWIFT_WHOLE_MODULE_OPTIMIZATION
, value 設(shè)為 YES
, 然后把優(yōu)化級(jí)別設(shè)為 None
就可以了.