App iOS 工程編譯優化實踐

引言

開發效率的提高,是開發者關注的一個永恆的話題。對於 iOS 而言,編譯速度一直是影響 iOS 開發和集成測試效率關鍵的一環。前端

攜程旅行 App iOS 工程編譯,經歷了從全源碼編譯到工程組件化,細分 Bundle,再到細分 Bundle 基礎上的進一步優化四個階段。每次的優化改造都是不斷結合業務反饋,深刻了解 xcode 編譯過程後的成果。python

iOS開發交流技術羣:563513413,無論你是大牛仍是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!c++

1、背景

簡單回顧一下在作 Bundle 拆分以前的狀況,當時整個 iOS 工程的全部代碼都在一塊兒,並未作工程拆分和解耦,編譯時全都是源碼編譯,數百萬行代碼所有編譯完成要將近一個小時。全部的開發人員都在一個工程裏開發,若是由於某我的提交的代碼有問題(這是經常會發生的),致使編譯了很長時間以後才報錯,更是耽誤時間,嚴重影響開發效率。對於測試人員來講,每次須要驗證一個功能時打包測試都須要至少等待幾十分鐘,這是極大的資源浪費。面試

這個時候的 Build 過程是全源碼 complie,幾千上萬個文件都須要編譯、連接,效率可想而知。swift

攜程旅行App iOS工程編譯優化實踐xcode

因此爲了提升開發和測試的效率,提升 iOS 工程的編譯速度刻不容緩。緩存

2、優化方案

2.1 工程組件化

第一個優化是把整個工程的編譯過程打散,把代碼按照業務線拆分紅一個個獨立的子工程,每一個子工程的編譯過程都是獨立的。每一個子工程只須要保證本身工程的源碼可以編譯成功,對外輸出統一的靜態庫和資源文件包的產物。這個產物咱們叫作 Bundle。app

單個業務工程(Bundle):框架

攜程旅行App iOS工程編譯優化實踐工具

App Build:

攜程旅行App iOS工程編譯優化實踐

對於單個業務來講,編譯時間大大縮短,整個 Build 過程變成單工程 complie,多工程 link,極大減小了 Build 過程當中的 complie 花費的時間。

這樣有兩個好處:

1)對於開發人員,每一個業務開發只須要把本身這個子工程切爲源碼引用,把其餘非本身模塊的子工程所有用靜態庫依賴,本地編譯也只須要編譯本身的子工程,能夠大大提高本地開發編譯速度。

2)對於測試人員,打包過程就變成了把全部已經編譯好的子 Bundle 靜態庫連接到一個殼工程裏,不須要對每一個文件進行編譯,能夠很快的打包測試驗證。

2.2 增量編譯

在工程組件化以後,在持續集成平臺上單個 Bundle 的打包時間仍是過長。所以框架團隊開始研究單個 Bundle 在持續集成平臺上增量編譯的可能性。

通過調研,最終選定 CCache 作爲解決方案。CCache 是一個編譯工具,能夠將 Xcode 編譯文件緩存起來,從而達到編譯提速。

針對本地開發該方案具備優點,可是在結合自研的移動發佈平臺 MCD(Mobile Continuous Delivery)(後面簡稱【發佈平臺】)上使用時效果並無達到預期,主要有兩點緣由:

1)同一 Bundle 多分支共存 :App 會存在大小版本同時開發的狀況,在發佈平臺中也就會存在不一樣版本、不一樣分支的狀況。

2)緩存管理不便 :發佈平臺打包機器一般僅有 250G 磁盤空間,當面臨磁盤壓力時,須要靈活的清理策略。

最終框架團隊採用了自管理,能作到緩存物理隔離,同時也就省去了環境配置的步驟。

增量編譯具體實現:

1)合併有變更的文件

  • 打包任務會根據新的 commitId 下載一份代碼副本,不能直接使用該副本,由於代碼文件內容沒有變更,僅僅是文件屬性的變更也會致使 xcodebuild 緩存不生效。所以須要副本和工做區內的源碼作 diff,僅僅合併內容有變更的文件。
  • 使用 python 的 filecmp 實現合併代碼邏輯,而且支持配置 ignore。
  • xcodebuild 指定 -derivedDataPath 設置緩存路徑,並將該目錄配置到 diff ignore 中。

2)提供清除緩存的功能

  • xcodebuild 的緩存有時候會出問題,好比修改了 c++ 文件後有時並不會生效,這種須要提供清除緩存的功能,能夠由開發自由選擇使用。

截止到以上兩步,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工程編譯優化實踐

2.3 Bundle 細分

最初攜程旅行 App 的 Bundle 都是按照業務來拆分的,好比:酒店就一個 Hotel Bundle,在當時編譯速度已經不慢了。可是隨着業務的發展,單個 Bundle 中業務代碼愈來愈多,文件愈來愈多,致使編譯又會變慢。這時,能夠將單個 Bundle 按照功能作更細粒度的拆分,好比酒店拆分出了酒店主工程、酒店基礎工程。

更細粒度的 Bundle 拆分還能帶來如下其餘收益:

  • 加快本地開發編譯:某個功能的開發人員只須要將本身這個功能模塊切爲源碼,其餘模塊全用靜態庫,提升本地開發編譯效率。
  • 爲其餘獨立 app 提供更細粒度的模塊功能支持:我廠的不少獨立 App 都是共用一套框架和基礎組件的,按功能模塊細粒度的拆分出獨立的模塊 Bundle 後,可使獨立 app 在選擇基礎組件時按需選擇。

2.4 合理設置頭文件搜索路徑

業務工程每每會大量依賴基礎庫代碼,在本工程編譯過程當中,也須要查找到引用的基礎代碼的頭文件。

由於代碼仍是在同一個倉庫裏,以前的方案是頭文件搜索設置仍是指向本地的基礎框架代碼,使用循環搜索的方式。

這樣的好處是任何一個頭文件的修改,使用方能夠立刻感知到。

缺點就是頭文件沒有特地爲方便調用進行組織,搜索起來特別費時。

通過統計,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%。

2.5 創建中央緩存

費雷德里克·布魯克斯說軟件工程領域沒有銀彈。經過以上優化後,減小了編譯時間,提高了開發和集成測試的效率,但這也不是解決編譯速度問題的銀彈。隨着業務的不斷使用,又出現了新的問題: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 中。

創建中央緩存還能帶來其餘好處:在發佈平臺作預緩存,使用定時任務更新中央緩存,進一步節省下載耗時。

該方案實際上採用的是空間換時間的策略,隨着時間推移,將會帶來磁盤不足的問題,因此必需要實現清理機制。

針對不一樣使用場景須要採用不一樣的緩存清理策略,具體以下:

  • 本地開發:該模式下,開發能夠自由選擇更新最新 Bundle 和僅更新配置,緩存使用不頻繁。因此將同一 Bundle 版本個數調低,緩存有效期拉長。
  • 持續集成:發佈平臺打包較爲頻繁,緩存使用比較頻繁,而且 Bundle 版本變更較快,因此將同一 Bundle 個數調高,緩存過時時間設置爲一天。

最終,打包耗時由原來的 7 分鐘降爲 5 分鐘。

3、存在的問題和思考

軟件開發工程沒有銀彈,你們都是在焦油坑裏掙扎。

Bundle 的方案節省了編譯的時間,提升了開發的效率,方便了持續集成和測試。

爲了提升單 Bundle 編譯速度而導出頭文件的方案,犧牲了必定的靈活性換來了編譯速度的提升。頭文件沒有了代碼中的直接搜索,框架開發人員從共同開發者真正變成了庫提供者,這就要求每一次都接口的修改都要及時更新並導出。

任何一個技術方案確定是在權衡各方面以後作出取捨的結果。框架團隊爲了提升 iOS Build 速度,經過自研的方案,作了拆分 Bundle,優化頭文件搜索路徑,增量編譯,創建中央緩存等步驟,基本上知足了現有我廠各業務線的平常開發需求。

相關文章
相關標籤/搜索