Swift編譯慢-優(yōu)化 - 優(yōu)化編譯速度

找出編譯耗時過長的文件

要優(yōu)化項目的編譯速度编兄,首先需要把耗時過長的文件找出來决乎,然后進(jìn)行重點優(yōu)化译打。這里會用到Xcode build的兩個OTHER_SWIFT_FLAGS

  • -Xfrontend: 如果編譯或類型檢查時耗時多長殖侵,則在Xcode中輸出警告。
  • -debug-time-function-bodies:輸出每個函數(shù)的編譯時長荧恍。

添加這些flag的方法為:

  1. 選中Target
  2. 選中Build Settings
  3. 搜索“Other Swift Flags”
  4. 添加”-Xfrontend -debug-time-function-bodies“
image

基于這兩個flag,有3個方法可以找到耗時過長的文件屯吊,這三個方法的本質(zhì)是一樣送巡,只不過是手動操作的多少而已,推薦使用第一種盒卸。

1.(推薦)使用BuildTimeAnalyzer

BuildTimeAnalyzer是一個macOS app骗爆,用于輔助分析Xcode的編譯log。

github下載這個app的源碼后蔽介,在Xcode打開項目摘投,點擊Product >> Archive >> Export,就可以生成BuildTimeAnalyzer虹蓄。雙擊運行看到如下的頁面犀呼,

image

按照使用說明一步步操作之后,就可以看到類似于下圖的界面薇组,點擊任意一行外臂,Xcode就會跳轉(zhuǎn)到對應(yīng)的函數(shù)中。

image

2. 命令行方式

命令行方式不需要修改Xcode的項目設(shè)置律胀,將下面命令行中的SwiftCompileTime替換為你的項目名宋光,在項目所在目錄下直接運行即可,它會將每個文件的編譯時間都輸出到culprits.txt中炭菌。這個命令執(zhí)行的時間可能會非常長罪佳!一定要耐心等待。

項目后綴名為.xcworkspace時使用:
xcodebuild -workspace SwiftCompileTime.xcworkspace -scheme SwiftCompileTime clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .[0-9]ms | grep -v ^0.[0-9]ms | sort -nr > culprits.txt

項目后綴名為.xcodeproj時使用:
xcodebuild -project SwiftCompileTime.xcodeproj -scheme SwiftCompileTime clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .[0-9]ms | grep -v ^0.[0-9]ms | sort -nr > culprits.txt

輸出文件的內(nèi)容的樣式如下:

210.61ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+TVGuor.swift:34:34   get {}
196.79ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+TVGuor.swift:34:34   get {}
128.94ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Optional+Extension.swift:23:28  get {}
123.50ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Optional+Extension.swift:23:28  get {}
119.40ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/VideoAlbum.swift:565:17 @objc public dynamic func formatVideoCount(by type: TVGVideoAlubmType) -> String
117.80ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+Extension.swift:109:23   get {}
114.67ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/NSObject+Extension.swift:13:32  get {}
112.09ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+Extension.swift:109:23   get {}
110.23ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/NSObject+Extension.swift:13:32  get {}
108.80ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Constants+Enum.swift:61:17  static func channelNo(fromChannelString channel: String?) -> Int?
103.16ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+Extension.swift:26:10    func urlQueryEncoded() -> String
102.63ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Constants+Enum.swift:61:17  static func channelNo(fromChannelString channel: String?) -> 

3.(不推薦)手動查看Xcode log方式

這個方式需要大量的手動操作黑低,效率很低赘艳,不推薦,列出來僅做參考投储。步驟如下:

  • 按照上面的教程第练,在Xcode中添加”-Xfrontend -debug-time-function-bodies“的flags
  • 編譯
  • 點擊?-9(Xcode 8上是?-8)跳轉(zhuǎn)到Build Report
  • 右擊build log >> Expand All Transcripts 就可以看到編譯時長的log了


    image
image

通過修改項目配置實現(xiàn)優(yōu)化

優(yōu)先級:高

在修改代碼之前,可以先通過修改Xcode的配置實現(xiàn)編譯速度的優(yōu)化

  • Optimization Level玛荞。
    • Build Settings -> Optimization Level -> Debug模式下設(shè)置為None娇掏。
    • Build Settings -> User-Defined -> 添加 SWIFT_WHOLE_MODULE_OPTIMIZATION = YES


      image
  • Debug模式下不生成dSYM文件。
    • dSYM(debug symbols file)是一個存儲debug信息的文件勋眯,每次編譯時都會生成婴梧。但在Debu模式下我們并不需要它下梢,所以可以將Debug的值設(shè)為"DWARF",僅在Release使用"DWARF with dSYM File".
image

避免使用簡潔表達(dá)式

這是一個讓人很糾結(jié)的優(yōu)化塞蹭。Swift提供了很多簡潔的表達(dá)方式孽江,比如定義變量時可以不用聲明變量的類型,使用??為optional提供默認(rèn)值等番电,這些功能為Swift開發(fā)者提供了不少便利岗屏,也構(gòu)成了Swift簡潔的編程風(fēng)格。不過它們在提供便利的同時漱办,也增加了不少編譯的時間这刷。如何抉擇,是令開發(fā)者頭疼的一個問題娩井。我個人的觀點是暇屋,對于那些并沒有提供太多便利的表達(dá)式,一定要替換為編譯更快的寫法洞辣;其他簡潔表達(dá)式咐刨,只要沒有顯著的增加編譯時長,繼續(xù)使用扬霜。

一定要避免使用String1 + String2

優(yōu)先級:高

一定要避免使用加好將兩個字符串合并起來定鸟,可以改用"\(string1)\(string2)"

"123" + string1 + "456"

替換為

"123\(string1)456"

一定要避免使用Array1 + Array2

優(yōu)先級:高

一定要避免使用加好將兩個數(shù)組合并起來畜挥,可以改用array1.append(contentsOf: array2)仔粥。

array1 + array2

替換為

array1.append(contentsOf: array2)

對于復(fù)雜變量的定義,添加類型聲明

優(yōu)先級:高

在Swift中定義變量時可以不帶類型聲明蟹但,編譯器會根據(jù)初始化的值去猜測變量類型躯泰,比如let str = "123"就定義了一個String。這為Swift開發(fā)者提供了很大的便利华糖,但編譯器分析變量類型是需要時間的麦向。從減少編譯時間的角度考慮,肯定是要給每個變量定義都加上類型聲明客叉,但這樣就完全不是在寫Swift的代碼了诵竭!

其實對于簡單的變量定義,比如let str = "123"let str: String = "123"兼搏,添不添加類型聲明卵慰,編譯時間都差不多,但對于下面兩個復(fù)雜的類型佛呻,最好還是加上type裳朋。

let myCompany = [
   "employees": [
        "employee 1": ["attribute": "value"],
        "employee 2": ["attribute": "value"],
        "employee 3": ["attribute": "value"],
        "employee 4": ["attribute": "value"],
        "employee 5": ["attribute": "value"],
        "employee 6": ["attribute": "value"],
        "employee 7": ["attribute": "value"],
        "employee 8": ["attribute": "value"],
        "employee 9": ["attribute": "value"],
        "employee 10": ["attribute": "value"],
        "employee 11": ["attribute": "value"],
        "employee 12": ["attribute": "value"],
        "employee 13": ["attribute": "value"],
        "employee 14": ["attribute": "value"],
        "employee 15": ["attribute": "value"],
        "employee 16": ["attribute": "value"],
        "employee 17": ["attribute": "value"],
        "employee 18": ["attribute": "value"],
        "employee 19": ["attribute": "value"],
        "employee 20": ["attribute": "value"],
    ]
]

// build time: 330ms
let tm1 = ceil(abs(PlayerWaitingStartInterval - timer.fireDate.timeIntervalSinceNow) * 1000)

// build time: 79ms
// Add type annotation
let tm1: Double = ceil(abs(PlayerWaitingStartInterval - timer.fireDate.timeIntervalSinceNow) * 1000)

// build time: 26ms
// Add type annotation & separate into two parts
let interval: Double = abs(PlayerWaitingStartInterval - timer.fireDate.timeIntervalSinceNow)
let tm1: Double = ceil(interval * 1000)

避免使用Nil-Coalescing operator ??

優(yōu)先級:低

??同樣會增加編譯時長。但我覺得這個優(yōu)化的優(yōu)先級并不高吓著,僅在編譯時長真的非常長時才考慮使用鲤嫡。

let name = string ?? ""

替換為

if let name = string { 
 /* string has value */
} else {
 /* string is nil*/
}

避免使用條件運算符?:

優(yōu)先級:低

?:??一樣會增加編譯時長送挑,但優(yōu)化的優(yōu)先級也不高。

let letter = isFirst ? "a" : "b"

替換為

var letter = ""
if isFirst { 
  letter = "a"
} else {
  letter = "b"
}

預(yù)計算

優(yōu)先級:中

不要直接在if-else的condition中做計算暖眼,會大大的增加編譯時長惕耕。可以先在外部創(chuàng)建一個變量保存計算好的值诫肠,再將這個變量作為condition司澎。

if number == 60 * 60 {}

替換為

let number: Double = 60 * 60
if number == 3600 {
}

Closures and lazy properties

優(yōu)先級:高

Lazy Property和Closures也可能導(dǎo)致編譯時長的增加。下面這個代碼是一個很普通的lazy Property定義栋豫,但它卻有可能導(dǎo)致過長的編譯時間惭缰。

// Lazy property
private(set) lazy var chartViewColors: [UIColor] = [
    self.chartColor,
    UIColor(red: 86/255, green: 84/255, blue: 124/255, alpha: 1),
    UIColor(red: 80/255, green: 88/255, blue: 92/255, alpha: 1),
    UIColor(red: 126/255, green: 191/255, blue: 189/255, alpha: 1),
    UIColor(red: 161/255, green: 77/255, blue: 63/255, alpha: 1),
    UIColor(red: 235/255, green: 185/255, blue: 120/255, alpha: 1),
    UIColor(red: 100/255, green: 126/255, blue: 159/255, alpha: 1),
    UIColor(red: 160/255, green: 209/255, blue: 109/255, alpha: 1),
    self.backgroundGradientView.upperColor
]

如果將它改為下面的closure后,編譯時間會更長笼才。

private let createChartViewColors = { () -> [UIColor] in
    let colors = [
        UIColor(red: 86/255, green: 84/255, blue: 124/255, alpha: 1),
        UIColor(red: 80/255, green: 88/255, blue: 92/255, alpha: 1),
        UIColor(red: 126/255, green: 191/255, blue: 189/255, alpha: 1),
        UIColor(red: 161/255, green: 77/255, blue: 63/255, alpha: 1),
        UIColor(red: 235/255, green: 185/255, blue: 120/255, alpha: 1),
        UIColor(red: 100/255, green: 126/255, blue: 159/255, alpha: 1),
        UIColor(red: 160/255, green: 209/255, blue: 109/255, alpha: 1),
    ]
    return colors
}

解決方案是將初始化代碼移到一個單獨的函數(shù)中。從代碼風(fēng)格的角度考慮络凿,將過長的初始化代碼移到單獨的函數(shù)中也是一個好習(xí)慣骡送,查看代碼時可以更加清晰地看出一個class定義了哪些property。

// Cumulative build time: 56.3ms
private(set) lazy var chartViewColors: [UIColor] = self.createChartViewColors()

// Build time: 6.2ms
private func createChartViewColors() -> [UIColor] {
    return [
        chartColor,
        UIColor(red: 86/255, green: 84/255, blue: 124/255, alpha: 1),
        UIColor(red: 80/255, green: 88/255, blue: 92/255, alpha: 1),
        UIColor(red: 126/255, green: 191/255, blue: 189/255, alpha: 1),
        UIColor(red: 161/255, green: 77/255, blue: 63/255, alpha: 1),
        UIColor(red: 235/255, green: 185/255, blue: 120/255, alpha: 1),
        UIColor(red: 100/255, green: 126/255, blue: 159/255, alpha: 1),
        UIColor(red: 160/255, green: 209/255, blue: 109/255, alpha: 1),
        backgroundGradientView.upperColor
    ]
}

奇怪的優(yōu)化

這里列出的是Swift編譯優(yōu)化中的一些奇怪現(xiàn)象絮记,感覺更像是編譯器的一些bug摔踱,不必花太多時間在這方便。

CGFloat轉(zhuǎn)化為CGFloat

優(yōu)先級:低

下面的代碼怨愤,將一個值為CGFloat的轉(zhuǎn)化為CGFloat時反而會導(dǎo)致編譯時長的大大增加派敷。這是一個很奇怪的現(xiàn)象,估計是編譯器的bug撰洗。

// Build time: 3431.7ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180

// Build time: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180

Round()

優(yōu)先級:低

下面的代碼只是調(diào)用了round()就增加了97%的編譯時長篮愉,很奇怪。

// Build time: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// Build time: 34.7ms
let expansion = a — b — c + d * 0.66 + e

References

Profiling your Swift compilation times

Uber: Swift with a hundred engineers

Speed up Swift compile time

Regarding Swift build time optimizations

Swift build time optimizations?—?Part 2

Improving Swift Compilation Times from 12 to 2 Minutes

作者:mobilefellow
鏈接:http://www.reibang.com/p/89e9df02e244
來源:簡書
簡書著作權(quán)歸作者所有差导,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處试躏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市设褐,隨后出現(xiàn)的幾起案子颠蕴,更是在濱河造成了極大的恐慌,老刑警劉巖助析,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犀被,死亡現(xiàn)場離奇詭異,居然都是意外死亡外冀,警方通過查閱死者的電腦和手機寡键,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锥惋,“玉大人昌腰,你說我怎么就攤上這事开伏。” “怎么了遭商?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵固灵,是天一觀的道長。 經(jīng)常有香客問我劫流,道長巫玻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任祠汇,我火速辦了婚禮仍秤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘可很。我一直安慰自己诗力,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布我抠。 她就那樣靜靜地躺著苇本,像睡著了一般。 火紅的嫁衣襯著肌膚如雪菜拓。 梳的紋絲不亂的頭發(fā)上瓣窄,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音纳鼎,去河邊找鬼俺夕。 笑死,一個胖子當(dāng)著我的面吹牛贱鄙,可吹牛的內(nèi)容都是我干的劝贸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼逗宁,長吁一口氣:“原來是場噩夢啊……” “哼悬荣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疙剑,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤氯迂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后言缤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚼蚀,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年管挟,在試婚紗的時候發(fā)現(xiàn)自己被綠了轿曙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖导帝,靈堂內(nèi)的尸體忽然破棺而出守谓,到底是詐尸還是另有隱情,我是刑警寧澤您单,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布斋荞,位于F島的核電站,受9級特大地震影響虐秦,放射性物質(zhì)發(fā)生泄漏平酿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一悦陋、第九天 我趴在偏房一處隱蔽的房頂上張望蜈彼。 院中可真熱鬧,春花似錦俺驶、人聲如沸幸逆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秉颗。三九已至,卻和暖如春送矩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哪替。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工栋荸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凭舶。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓晌块,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帅霜。 傳聞我的和親對象是個殘疾皇子匆背,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 找出編譯耗時過長的文件 要優(yōu)化項目的編譯速度,首先需要把耗時過長的文件找出來身冀,然后進(jìn)行重點優(yōu)化钝尸。這里會用到Xcod...
    mobilefellow閱讀 2,211評論 2 8
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_x閱讀 15,968評論 3 119
  • 今天穿上了旗袍加紅色的外披搂根,希望大寶能夠旗開得勝珍促,鴻運當(dāng)頭。我們在家里忐忑的等著考試的消息剩愧。人家一考完試立馬就...
    番茄媽閱讀 115評論 0 0
  • 你為什么活得那么累? 當(dāng)這個問題拋向你的時候穴翩,不知道你的第一反應(yīng)是什么犬第,我腦子里晃過的是三個字——不甘心。 我們常...
    紫JZZ閱讀 356評論 1 4
  • 簡介: 他霸道腹黑芒帕,邪笑著歉嗓,修長的手指輕易地勾起她的下巴:“薰衣,別忘了小時候的約定副签,你遥椿,必須嫁給我∠ⅲ”冠场;他溫柔似...
    瓶九玖閱讀 172評論 1 1