開發效率的提高,是開發者關注的一個永恆的話題。對於 iOS 而言,編譯速度一直是影響 iOS 開發和集成測試效率關鍵的一環。前端
攜程旅行 App iOS 工程編譯,經歷了從全源碼編譯到工程組件化,細分 Bundle,再到細分 Bundle 基礎上的進一步優化四個階段。每次的優化改造都是不斷結合業務反饋,深刻了解 xcode 編譯過程後的成果。python
iOS開發交流技術羣:563513413,無論你是大牛仍是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!c++
簡單回顧一下在作 Bundle 拆分以前的狀況,當時整個 iOS 工程的全部代碼都在一塊兒,並未作工程拆分和解耦,編譯時全都是源碼編譯,數百萬行代碼所有編譯完成要將近一個小時。全部的開發人員都在一個工程裏開發,若是由於某我的提交的代碼有問題(這是經常會發生的),致使編譯了很長時間以後才報錯,更是耽誤時間,嚴重影響開發效率。對於測試人員來講,每次須要驗證一個功能時打包測試都須要至少等待幾十分鐘,這是極大的資源浪費。面試
這個時候的 Build 過程是全源碼 complie,幾千上萬個文件都須要編譯、連接,效率可想而知。swift
攜程旅行App iOS工程編譯優化實踐xcode
因此爲了提升開發和測試的效率,提升 iOS 工程的編譯速度刻不容緩。緩存
第一個優化是把整個工程的編譯過程打散,把代碼按照業務線拆分紅一個個獨立的子工程,每一個子工程的編譯過程都是獨立的。每一個子工程只須要保證本身工程的源碼可以編譯成功,對外輸出統一的靜態庫和資源文件包的產物。這個產物咱們叫作 Bundle。app
單個業務工程(Bundle):框架
攜程旅行App iOS工程編譯優化實踐工具
App Build:
攜程旅行App iOS工程編譯優化實踐
對於單個業務來講,編譯時間大大縮短,整個 Build 過程變成單工程 complie,多工程 link,極大減小了 Build 過程當中的 complie 花費的時間。
這樣有兩個好處:
1)對於開發人員,每一個業務開發只須要把本身這個子工程切爲源碼引用,把其餘非本身模塊的子工程所有用靜態庫依賴,本地編譯也只須要編譯本身的子工程,能夠大大提高本地開發編譯速度。
2)對於測試人員,打包過程就變成了把全部已經編譯好的子 Bundle 靜態庫連接到一個殼工程裏,不須要對每一個文件進行編譯,能夠很快的打包測試驗證。
在工程組件化以後,在持續集成平臺上單個 Bundle 的打包時間仍是過長。所以框架團隊開始研究單個 Bundle 在持續集成平臺上增量編譯的可能性。
通過調研,最終選定 CCache 作爲解決方案。CCache 是一個編譯工具,能夠將 Xcode 編譯文件緩存起來,從而達到編譯提速。
針對本地開發該方案具備優點,可是在結合自研的移動發佈平臺 MCD(Mobile Continuous Delivery)(後面簡稱【發佈平臺】)上使用時效果並無達到預期,主要有兩點緣由:
1)同一 Bundle 多分支共存 :App 會存在大小版本同時開發的狀況,在發佈平臺中也就會存在不一樣版本、不一樣分支的狀況。
2)緩存管理不便 :發佈平臺打包機器一般僅有 250G 磁盤空間,當面臨磁盤壓力時,須要靈活的清理策略。
最終框架團隊採用了自管理,能作到緩存物理隔離,同時也就省去了環境配置的步驟。
增量編譯具體實現:
1)合併有變更的文件
2)提供清除緩存的功能
截止到以上兩步,Native 已經基本實現了增量編譯,可是實際使用還不夠。由於打包主要是在集成系統平臺上面完成的,集成平臺打包有多臺機器。
攜程旅行 App 的打包 Jenkins 採用的是 master-slave 模式,一個 Job 下會有多個節點,Job 是隨機抽取的節點。爲了提升增量編譯的命中率,必需要讓 Bundle 和節點關聯起來。好比:有 ABCD 四個節點,HotelBundle 每次都落到 A 節點,這樣才能保證 A 節點中 HotelBundle 的 xcodebuild 緩存有效,而且代碼 diff 差別最小。
具體實現:
1)保留 Jenkins Job 的工做區
該步驟是在 Jenkins Job 的配置中操做,取消勾選下圖中的 Delete workspace before build starts
攜程旅行App iOS工程編譯優化實踐
2)使用 Jenkins 插件創建 Bundle 和節點的關聯
基於 Jenkins Label Parameter Plugin,並作改造,實現僞隨機,以保證關聯的節點下線以後,能使用候補節點正常工做。
發佈平臺前端提供關聯配置,業務能夠按需選擇使用。
攜程旅行App iOS工程編譯優化實踐
經過以上步驟就實現了增量編譯,可是該方案針對 swift 不生效。swift 在 Release 模式採用的全量編譯(以下圖), 作總體優化。不過 swift Bundle 能夠採用上述 Bundle 拆分的方案。
攜程旅行App iOS工程編譯優化實踐
以某一個編譯源碼文件 197 個、資源文件 142 個的 Bundle 爲例看下效果。
攜程旅行App iOS工程編譯優化實踐
採用增量編譯後,Bundle 編譯耗時由 116s 降爲 9s。
攜程旅行App iOS工程編譯優化實踐
最初攜程旅行 App 的 Bundle 都是按照業務來拆分的,好比:酒店就一個 Hotel Bundle,在當時編譯速度已經不慢了。可是隨着業務的發展,單個 Bundle 中業務代碼愈來愈多,文件愈來愈多,致使編譯又會變慢。這時,能夠將單個 Bundle 按照功能作更細粒度的拆分,好比酒店拆分出了酒店主工程、酒店基礎工程。
更細粒度的 Bundle 拆分還能帶來如下其餘收益:
業務工程每每會大量依賴基礎庫代碼,在本工程編譯過程當中,也須要查找到引用的基礎代碼的頭文件。
由於代碼仍是在同一個倉庫裏,以前的方案是頭文件搜索設置仍是指向本地的基礎框架代碼,使用循環搜索的方式。
這樣的好處是任何一個頭文件的修改,使用方能夠立刻感知到。
缺點就是頭文件沒有特地爲方便調用進行組織,搜索起來特別費時。
通過統計,Hotel 一個文件的編譯每每都是秒級別。一整個工程編譯下來就是十幾分鍾。
所以框架團隊意識到必需要和第三方庫同樣,在目前的.a 和資源文件以外,提交 include 目錄包含全部會被外部使用的頭文件。
同時,考慮到 iOS 開發向 Swift 轉型的須要,若是在 include 目錄的基礎上,還可以提供一份基於 include 裏頭文件的 module.mapmodule 文件。將方便後期業務方向 Swift 的遷移。
具體方法是:
1)首先框架的 Bundle,在工程設置中點擊工程的 Target→Build Phases→Copy Files 點擊 +,輸入.h 把須要暴露的頭文件都添加上。
這樣會在輸出產物的 Build 目錄下,多一個 include 目錄,再經過腳本去把這個目錄裏面的全部文件複製出來,同時生成 module.mapmodule。
2)使用的時候,將頭文件搜索路徑設置到 include 目錄,而且設置爲非遞歸搜索。
攜程旅行App iOS工程編譯優化實踐
驗證下來,Hotel 工程修改以後的 Build 時間爲 7 分鐘,相比修改以前的 19 分鐘,時間減小了 63%。
費雷德里克·布魯克斯說軟件工程領域沒有銀彈。經過以上優化後,減小了編譯時間,提高了開發和集成測試的效率,但這也不是解決編譯速度問題的銀彈。隨着業務的不斷使用,又出現了新的問題:Bundle 拉取時間過長。
Bundle 化方案各個業務的靜態庫生成都是在發佈平臺上編譯的,業務在本地開發的時候再使用框架的腳本拉取 bundle 到本地。發佈平臺上打測試包的時候也是須要拉取全部 Bundle。
發佈平臺打包過程以下:
1)初始化 Jenkins 工做區,下載代碼副本
2)下載 Bundle
3)使用 xcodebuild 生成 ipa
4)上傳 ipa 和符號表
5)Job 狀態回調
整個過程共耗時 7 分鐘,目前攜程旅行 App iOS 最新的版本的上線 Bundle 將近 70 個,每一個 Bundle 的靜態庫支持 arm6四、x84_64 等指令集,全部 Bundle 加起來有 4G 大小,即便在內網全量下載耗時也要 2~3 分鐘。
好比酒店某一 Bundle:
攜程旅行App iOS工程編譯優化實踐
全部 Bundle 全量更新一次耗時:
攜程旅行App iOS工程編譯優化實踐
針對這個問題,解決方案是創建中央緩存。
在用戶根目錄下,創建一個隱藏的目錄.iOSBundleRepo,按照 Bundle 的版本號存儲,同一 Bundle 可存在多個版本。工具下載 Bundle 時優先判斷緩存,未命中時纔開始下載而且緩存到 repo 中。
創建中央緩存還能帶來其餘好處:在發佈平臺作預緩存,使用定時任務更新中央緩存,進一步節省下載耗時。
該方案實際上採用的是空間換時間的策略,隨着時間推移,將會帶來磁盤不足的問題,因此必需要實現清理機制。
針對不一樣使用場景須要採用不一樣的緩存清理策略,具體以下:
最終,打包耗時由原來的 7 分鐘降爲 5 分鐘。
軟件開發工程沒有銀彈,你們都是在焦油坑裏掙扎。
Bundle 的方案節省了編譯的時間,提升了開發的效率,方便了持續集成和測試。
爲了提升單 Bundle 編譯速度而導出頭文件的方案,犧牲了必定的靈活性換來了編譯速度的提升。頭文件沒有了代碼中的直接搜索,框架開發人員從共同開發者真正變成了庫提供者,這就要求每一次都接口的修改都要及時更新並導出。
任何一個技術方案確定是在權衡各方面以後作出取捨的結果。框架團隊爲了提升 iOS Build 速度,經過自研的方案,作了拆分 Bundle,優化頭文件搜索路徑,增量編譯,創建中央緩存等步驟,基本上知足了現有我廠各業務線的平常開發需求。