本文主要內(nèi)容:
一褥伴、背景
二漾狼、效果展示
三、接入插件及demo地址
四似踱、聊聊開發(fā)插件前期的編譯優(yōu)化調(diào)研
五核芽、技術選型后二進制插件的開發(fā)
六酵熙、實際使用后遇到的問題
七绿店、總結
八庐橙、參考文獻
一态鳖、背景
由于團隊也是組件化的開發(fā)模式,而且有較多的app浸须。大的C端產(chǎn)品打包要十幾二十分鐘删窒,小的業(yè)務app打包也要八九分鐘顺囊。那如何提升項目的編譯效率特碳,和打包速度晕换,就是一個需要去處理的課題闸准。這款插件靈活便捷梢灭,可能符合你需要的提效能力或辖。
二颂暇、先看效果
執(zhí)行pod install后的展示
二進制后工程內(nèi)pod組件結構
緩存結構目錄
編譯前后對比
具體提效程度看不同工程組件化情況湿蛔,可以拿去到自己項目里感受下阳啥。我們的工程多以OC和OC+Flutter為主财喳,也有集成Swift的組件耳高,還沒有試過純Swift的項目,如果插件使用有問題可以提供demo概荷,我來優(yōu)化下插件碌燕。
三修壕、先去立刻體驗回來再看后續(xù)內(nèi)容
gem install cocoapods-zjbinary
可配合demo工程一起食用,如果插件好用改鲫,可以點個??
https://github.com/Byte10/BinaryDemo
四像棘、聊聊開發(fā)插件前期的編譯優(yōu)化調(diào)研
iOS 微信編譯速度優(yōu)化分享
https://cloud.tencent.com/developer/article/1564372
他們的方案總結是以下:
A缕题、優(yōu)化頭文件搜索路徑
B、關閉 Enable Index-While-Building Functionality
C瘪松、優(yōu)化 PB/模版锨阿,減少冗余代碼
D墅诡、使用 PCH 預編譯
E、使用工具優(yōu)化頭文件引入烟馅;盡量避免頭文件里包含 C++ 標準庫
pod層的優(yōu)化方案:
https://www.dandelioncloud.cn/article/details/1498967543334379522 貝聊科技如何將 iOS 項目的編譯速度提高5倍這片文章郑趁。
他們調(diào)研了下面幾種方案:
1寡润、cocoapods-packager
cocoapods-packager 可以將任意的 pod 打包成 Static Library舅柜,省去重復編譯的時間业踢,一定程度上可以加快編譯時間礁扮,但是也有自身的缺點:
優(yōu)化不徹底太伊,只能優(yōu)化第三方和私有 Pod 的編譯速度,對于其他改動頻繁的業(yè)務代碼無能為力
私有庫和第三方庫的后續(xù)更新很麻煩僚焦,當有源碼修改后,需要重新打包上傳到內(nèi)部的 Git 倉庫
過多的二進制文件會拖慢 Git 的操作速度(目前還沒部署 Git 的 LFS)
難以調(diào)試源碼
2边坤、Carthage
這個方案跟 cocoapods-packager 比較類似谅年,優(yōu)缺點都差不多融蹂,但 Carthage 可以比較方便地調(diào)試源碼缺虐。
因為我們目前已經(jīng)大規(guī)模使用 CocoaPods渐苏,轉用 Carthage 來做包管理需要做大量的轉換工作拆檬,所以不考慮這個方案了洽瞬。
3伙窃、Buck
Buck 是一套通用的構建系統(tǒng),由 Facebook 開源晦闰。最大的特色是智能的增量編譯可以極大地提高構建速度呻右。最早聽說 Buck 的時候鞋喇,它還只能用在安卓上侦香,現(xiàn)在已經(jīng)適配了 iOS。
它能增快構建速度的主要原因是緩存了編譯結果憾赁,通過持續(xù)監(jiān)視項目目錄的文件變化龙考,每次編譯時只編譯有改動的文件晦款。另外一個讓我很受啟發(fā)的功能是 HTTP Cache Server,通過一臺緩存文件服務器來保存大家的編譯結果亡问,這樣只要團隊里其中一人編譯過的文件州藕,其他人就不用再編譯了酝陈,直接下載就行沉帮。
Buck 是個相當完備的解決方案穆壕,很多國外的大公司例如 Uber 都已經(jīng)用上。他們也花了很多時間來研究缨该,最終還是認為對他們的項目和團隊來說贰拿,目前并不是很適合熄云,主要原因是:
Buck 拋棄了 Xcode 的項目文件缴允,需要手工編寫配置文件來指定編譯規(guī)則,這要對現(xiàn)有項目作出大幅度的調(diào)整矗漾。他們目前還在快速迭代新功能,沒有余暇和人手來實施都办。
開發(fā)和調(diào)試的流程都得做出很大的改變。因為 Buck 接管了項目編譯的過程势木,想調(diào)試項目不能簡單地在 Xcode 里面 ?+R 了啦桌,得先反過來讓 Buck 生成 Xcode 的項目文件甫男。Uber 的工程師甚至推薦使用 Nuclide 來代替 Xcode 作為開發(fā)環(huán)境验烧。雖然原理上是可行的碍拆,但是團隊需要花不少時間來適應感混,短期內(nèi)效率降低無可避免。
用 Xcode 調(diào)試代碼享受不到加快編譯速度的好處婆跑。雖然可以用 buck 命令啟動 App洽蛀,然后在命令行里啟動 lldb 來調(diào)試郊供,但那就無法使用 Xcode 的調(diào)試工具 例如 View Debugging 和 Memory Graph Debugger驮审。
4吉执、Bazel
Bazel 跟 Buck 很相似戳玫,是 Google 開源的咕宿,優(yōu)缺點跟 Buck 都差不多,不再詳細說了缆镣。
5董瞻、distcc 分布式編譯
原理是把一部分需要編譯的文件發(fā)送到服務器上钠糊,服務器編譯完成后把編譯產(chǎn)物傳回來。他們嘗試了一下比較出名的 distcc煞聪,搭建過程比較簡單昔脯,最后也能成功地把編譯任務分派到內(nèi)網(wǎng)的多臺服務器上云稚。但是其他編譯服務器的 CPU 占用總是很低静陈,只有 20% 左右诞丽;也就是說分派任務的速度甚至還趕不上服務器編譯的速度僧免,分派任務然后回傳編譯產(chǎn)物這個過程所耗費的時間超過了本地直接編譯懂衩。不停調(diào)整參數(shù)反復試驗了很多次,最后發(fā)現(xiàn)編譯時間完全沒有變快浊洞,甚至還有點變慢了牵敷。可能以他們目前項目的規(guī)模并不適合使用分布式編譯法希。
6枷餐、CCache 是他們最后選擇的方案
大致原理就是將上次的編譯產(chǎn)物緩存起來,在下一次編譯時會檢查是否命中緩存苫亦,如果命中緩存會優(yōu)先取上一次的編譯產(chǎn)物
經(jīng)過在工程中的一番嘗試毛肋,總結出了以下幾個特點
- CCache 確實能夠很大程度上提高編譯速度
- CCache 的緩存命中率相對穩(wěn)定
- CCache 不支持 PCH 文件
- CCache 不支持 Clang-Moudle 的方式
- CCache 目前不支持 Swift
7奕锌、最后看到了有贊的二進制方案
https://tech.youzan.com/you-zan-ji-yu-er-jin-zhi-de-bian-yi-ti-xiao-ce-lue/
感覺還是相對契合我們團隊一些,不過沒有開源村生,那我們自己搞下好了。
五饼丘、動手搞二進制插件
Cocoapods-Binary(Cocoapods 官方推薦的二進制插件)他提供了很好的思路趁桃,以及可以教會我如何整一個cocoapods插件。
萬事開頭難肄鸽,這個插件的源碼卫病,已經(jīng)可以讓我開頭了。
經(jīng)過對業(yè)界常用方法的探索典徘,總結出了以下三種二進制化使用的常見方案:
-
即時生成二進制包并緩存
類 Carthage 的實現(xiàn)思路蟀苛,以 Cocoapods-Binary(Cocoapods 官方推薦的二進制插件),在 pod install 后逮诲,對本次編譯帜平,即時生成二進制包并緩存,缺點是在沒有對應二進制包版本時梅鹦,pod install 后會額外去做二進制包的生成裆甩,一定程度上會影響 pod install的速度,并且如果開發(fā)者切回源碼調(diào)試齐唆,二進制緩存會一并清空
-
單私有源嗤栓,PodSpec 包含源碼及二進制信息
單私有源指的是 Pod 庫均包含在同一個 Repo 內(nèi),二進制的
vendor_libraries
/vendor_frameworks
等信息會存在于各個 Pod庫對于 PodSpec 的 SubSpec 中箍邮,在 Podfile 中讀取二進制相關配置去決定是否使用二進制SubSpec缺點是源碼與二進制并存與一處茉帅,不僅會讓 PodSpec 顯得臃腫,并且會增大 Source 源的體積锭弊,降低 Pod 庫的 Download 速度以及 Lint 速度堪澎,以及多 SubSpec 的模式也會影響最終生成 xcworkspace 的速度
-
多私有源
多私有源指的是源碼與二進制分別獨立,使用兩個不同的 Source廷蓉,二進制文件一般壓縮存于靜態(tài)服務器中全封,以空間去換取時間效率,同時存在的問題是桃犬,Source 之間的切換問題刹悴,二進制包以及 Spec 的生成時機問題
那結合我們團隊現(xiàn)狀,我采取多私有源的方案攒暇,不過不一樣的是土匀,打算將二進制的緩存放在本地,而非服務器中形用,這樣可以做到不依賴服務器就轧,而且省去了制作二進制repo的開發(fā)消耗证杭,只需要本地消耗一些硬盤空間,這對于我們團隊是非常低的成本妒御〗夥撸可以有打包機一次構建后,緩存下來的二進制文件乎莉,同步給開發(fā)同學后送讲,即可省去第一次接入二進制的大部分時間。而且除了緩存機制外惋啃,預期還需要達到以下效果:
- 開發(fā)同學無需額外改動組件
- 開發(fā)同學接入無需太多成本
- 支持源碼切換和調(diào)試
- 支持部分組件不用二進制
- 二進制一次編譯后續(xù)直接從緩存讀取
1哼鬓、Cocoapods-Binary接入初體驗
- pod install 后進行做二進制包的生成,但是只支持framework
- 開發(fā)者切回源碼調(diào)試边灭,二進制緩存會一并清空异希,重新編譯
- 由于只支持framework,引入方式變更為 #import <aaa/aaa.h>绒瘦,這個對于我們團隊大部分是以#import "aaa.h"會是個很大的改造量
2称簿、要改造的內(nèi)容
- 二進制包生成后,添加拷貝到緩存的邏輯
- 除了framework還支持.a惰帽,就可以解決頭文件引用的問題予跌,目前的項目就不需要大量時間成本進行頭文件引用改造
是不是看起來好像只要改完這兩點就大功告成了,確實是這樣善茎。
3券册、添加編譯成.a的邏輯
二進制包的構建有以下方式
- cocoapods-packager 插件
cocoapods-packager 是一款開源的二進制打包的 pod 插件,通過源碼 podspec 生成 Podfile垂涯,pod install 生成包含對應 Pod 庫的工程烁焙,之后通過 xcodebuild 去構建 .a / .framework,在看過該庫的源碼后發(fā)現(xiàn)該邏輯并不復雜耕赘,但是在嘗試之后會發(fā)現(xiàn)幾個問題:
當選擇 .a 形式作為產(chǎn)物時骄蝇,我們 podspec 中所指定的 .h 并不會被正確拷貝到目標文件夾
該組件對 Subspec 的處理較為暴力,會將多個 Subspec 合并為一個操骡,例如我一個組件庫九火,Phone 工程需要引用SubSpecA,Pad工程需要引用 SubSpecB册招,在使用該組件打包時岔激,會將 SubSpecA 與 SubSpecB 合并為一個 framework/.a,這種情況顯然不是我們所需要的是掰,更為合理的做法是可通過配置去設置虑鼎,是否將 SubSpec 進行合并或拆分
cocoapods-packager 已經(jīng)停止維護,在對 Cocoapods 新特性或者 Swift 的支持上無法達到同步更新
- 自行編寫打包腳本只需要使用xcodebild即可
// 構建真機架構
xcodebild -project Pods.xcodeproj -scheme xxx -configuration Release -sdk iphoneos
// 構建模擬器架構
xcodebild -project Pods.xcodeproj -scheme xxx -configuration Release -sdk iphonesimulator
再合成下我們的兩個庫文件
lipo -create '模擬器架構文件' '真機架構文件' -output '目標庫'.a
那至此,我們編譯.a的邏輯就完成了炫彩。
4匾七、添加緩存的邏輯
這個需要在對外提供的屬性添加一個變量,如
set_local_binary_cache_path '/Users/xxx/podBinaryCache'
然后根據(jù)變量設置情況江兢,判斷該組件版本是否在緩存庫里昨忆,不在的話,編譯完二進制后杉允,拷貝一份扔嵌,下次直接從緩存里讀取該組件。
考慮到這個目錄不同電腦上地址取不到夺颤,于是添加了這個屬性,設置二進制默認緩存地址胁勺,開啟后默認地址為:/Users/xxx/Desktop/podCache,方便打包機及團隊內(nèi)同學讀取同一份二進制緩存地址
set_default_desktop_cache_path!
當然因為有.a和framework的區(qū)別世澜,將緩存添加了區(qū)分分別為library和framework。framework下再區(qū)分靜態(tài)庫和動態(tài)庫署穗。
5寥裂、源碼調(diào)試的邏輯
這個參考了這位大兄弟的思路
https://github.com/Jacky-LinPeng/cocoapods-xlbuild
6、資源的獲取
可以根據(jù)resource_paths拿到這個組件的資源目錄
resources = target.resource_paths
resource_paths = resources[target_name]
resource_paths.each do |path|
path_names = path.to_s.split('${PODS_ROOT}')
end
六案疲、實際使用后遇到的問題
1封恰、.a的生成要以lib開頭不然會有問題,比如AFNetwroking.a要是libAFNetworking.a
2褐啡、.a以xcodebuild生成后诺舔,沒有頭文件,這沒法用备畦。經(jīng)過一段時間的分析低飒,找到了這個屬性,這里面的東西不就是我要的頭文件嘛
pod_target_header_mappings = target.header_mappings_by_file_accessor.values
3、資源文件的問題懂盐,處理資源文件需要放入組件文件夾中
1)如果組件本身包含bundle文件褥赊,直接拷貝進去
2)如果組件本身資源文件在assets中莉恼,則打包后尿背,拷貝bundle文件放入
4残家、處理本地pod導入問題
本地引入組件會有Local Podspecs 需要提前在Pods/ 中生成該文件夾茴晋,然后拷貝才不會有問題
5、文件目錄帶有空格符號的問題
處理路徑的時候加了.shellescape即可解決
七烁涌、總結
處理完那些問題后撮执,這個二進制插件基本運行沒有什么問題了,也打到了前面提到的幾個預期谋币,在團隊中至今也穩(wěn)定運行了半年,編譯效率確實有不少提升诅蝶。借鑒了不少業(yè)內(nèi)大佬的經(jīng)驗,嘗試了不少編譯優(yōu)化方案筐眷,學習了ruby插件的開發(fā),解決了一些使用上遇到的問題武翎,熟悉了iOS庫文件的相關知識和cocoapods的源碼,后續(xù)將分享靜態(tài)庫垫毙、動態(tài)庫的學習經(jīng)驗综芥。關于cocoapods插件的學習屠阻,大家可以看下cocoapods-binary的源碼额各。
具體的使用方法都在這里了虾啦,好用的話可以點下?? 謝謝!
https://github.com/Byte10/BinaryDemo
掘金地址:https://juejin.cn/post/7188803862612934713
八需频、參考文獻
https://cloud.tencent.com/developer/article/1564372
https://www.dandelioncloud.cn/article/details/1498967543334379522
https://tech.youzan.com/you-zan-ji-yu-er-jin-zhi-de-bian-yi-ti-xiao-ce-lue/