iOS包體積優(yōu)化實(shí)踐
作者丨Augus
來源丨搜狐技術(shù)產(chǎn)品(ID:sohu-tech)
本文字?jǐn)?shù):7972字
預(yù)計閱讀時間:20分鐘
?用最通俗的語言,描述最難懂的技術(shù)
前情描述
最近領(lǐng)導(dǎo)給我分配了包體積監(jiān)控指標(biāo),恰好之前我也做過相關(guān)包體積的優(yōu)化,于是便綜合之前的實(shí)踐經(jīng)驗,對此我們的項目的包體積優(yōu)化做一個整體的記錄,希望可以幫助有需要的小伙伴
iOS(安裝)包
ipa全稱為iPhone Application Archive,這里指的包在iOS其實(shí)就是安裝包,直觀展示就是xxx.ipa文件
iOS包內(nèi)容
想要對其優(yōu)化,就要知道包里面都有什么,知道了有什么,針對不同類型的文件,做不同的處理,從而達(dá)到一定的優(yōu)化
我們首先找到一個xxx.ipa文件,然后把后綴改為xxx.zip,然后進(jìn)行解壓縮操作,如下圖所示

然后右鍵選中顯示包內(nèi)容,里面就是安裝包包含的所有資源,大概可以分為以下幾類
_CodeSignature文件夾:ipa包簽名文件的存放文件夾Assets.car:Assets.xcassts在編譯過程中生成的最終展示文件,默認(rèn)里面存放各種分辨率圖片(測試項目未使用)embedded.mobileprovision:證書配置文件Info.plist:項目配置表Plugins:App創(chuàng)建的擴(kuò)展,比如Widget、Push和Share等.Iproj:App所支持的語言文件exec文件:可執(zhí)行文件,例如widgetExtension圖片資源: .png,.jpg,.webp,.gif其它資源文件 .xml,.json.plist:項目中使用資源的.plist文件.bundle:Mac OS下的資源包集合.conf:相關(guān)的配置文件.cer,.der,.p12:鑰匙串文件.wav:音頻文件.js,.html.nib:Xcode自帶的數(shù)據(jù)文件,包含一個窗口程序和應(yīng)用程序委托對象.sqlite:數(shù)據(jù)庫文件.txt:文本文件.mom:Xcode創(chuàng)建的數(shù)據(jù)模型文件
這次測試之前IPA未解壓的大小是76.5MB,解壓之后的大小是140.1MB
解壓后各個部分大小的明細(xì)如下
| 內(nèi)容 | 大小 |
|---|---|
_CodeSignature | 1.8MB |
Assets.car(部分使用) | 518KB |
embedded.mobileprovision | 55KB |
Info.plist | 16KB |
Plugins | 2.7MB |
Iproj | 2KB |
exec | 104.9MB |
圖片資源(.png,.jpg,.webp,.gif) | 16.7MB |
others | 13.4MB |
優(yōu)化策略
上述了解完了IPA各個組成之后,我們按照Xcode編譯優(yōu)化,資源文件優(yōu)化以及代碼優(yōu)化的順序來一步一步進(jìn)行分析
Xcode編譯優(yōu)化
這個部分是最容易的,也是最容易忽略的,下面是筆者進(jìn)行的總結(jié),我們一個一個分析
編譯指令集
首先查看我們目前的架構(gòu)組成
augus@MacBookPro?xxx.app?%?lipo?-info?xxx
Architectures?in?the?fat?file:?sohunews?are:?armv7?arm64
我們再看下各個架構(gòu)指令集對應(yīng)的機(jī)型
#?armv6:?iPhone,?iPhone?3G,?iPod?1G/2G??
#?armv7:?iPhone?3GS,?iPhone?4,?iPhone?4S,?iPod?3G/4G/5G,?iPad,?iPad?2,?iPad?3,?iPad?Mini??
#?armv7s:?iPhone?5,?iPhone?5c,?iPad?4??
#?arm64:?iPhone?X,iPhone?8(Plus),iPhone?7(Plus),iPhone?6(Plus),iPhone?6s(Plus),?iPhone?5s,?iPad?Air(2),?Retina?iPad?Mini(2,3)??
#?arm64e:?XS/XS Max/XR/ iPhone 11, iPhone 11 pro,iPhone 11 Pro Max,iPhone SE (2nd generation),iPhone 12 mini,iPhone 12,iPhone 12 Pro,iPhone 12 Pro Max,Phone 13 mini,Phone 13,iPhone 13 Pro,iPhone 13 Pro Max
#?x86_64:?模擬器64位處理器
#?i386:?模擬器32位處理器
此次測試中我們是不需要32位架構(gòu)armv7的,所以我們可以設(shè)置在打包的時候進(jìn)行只打armv64即可。
方法1:Build Settings-Architectures,把你需要的打包標(biāo)識符下的設(shè)置為arm64;Architectures指定工程可以編譯出多個指令集的代碼包,ipa就會變大;
方法2:Build Settings-Excluded Arcitectures,在你要的打包標(biāo)識符下面增加兩個配置項目Any iOS SDK和Any iOS Simulator SDK,然后分別設(shè)置為armv7和arm64;這個選項的意思是Release模式下針對真機(jī)armv7指令集排除,針對模擬器把arm64排除。

這個優(yōu)化之后,ipa壓縮包大小為51.4MB,解壓縮包大小變?yōu)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">90.9MB,其中exec變?yōu)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">56.6MB,效果很明顯
代碼優(yōu)化
Build Settings-Optimization Level在發(fā)布模式設(shè)置為[-Oz],其他模式根據(jù)場景進(jìn)行選擇,具體參考官方文檔(https://help.apple.com/xcode/mac/11.4/#/itcaec37c2a6)進(jìn)行設(shè)置;這個設(shè)置指的是生成的代碼在速度和二進(jìn)制大小方面的優(yōu)化程度

Optimization Level默認(rèn)是-Os,-Oz是Xcode 11新增的編譯優(yōu)化選項,該設(shè)置通過將重復(fù)的代碼模式隔離到編譯器生成的函數(shù)中來實(shí)現(xiàn)額外的尺寸節(jié)省
以下是不同的選項對應(yīng)的編譯速度和二進(jìn)制文件大小變化趨勢,來源于WWDC2019 Session 409 https://developer.apple.com/videos/play/wwdc2019/409/

優(yōu)化完成之后ipa大小是51MB,解壓縮包大小是87.9MB
資源目錄優(yōu)化
Build Settings-Asset Catalog Compiler Options-Optimization設(shè)置為space;這個選項可以改變actool在構(gòu)建Assets.car時選取的編碼壓縮算法,減少包大小。
使用以下命令檢查Assets.car中圖片的編碼壓縮算法
#?可以把對應(yīng)信息生成.json文件,用于對比不同
xcrun?--sdk?iphoneos?assetutil?--info?Assets.car?>?Assets.json

項目之前用的是默認(rèn)配置,由于Assets.car存放了少量資源,大部分都是存放在項目的根目錄;
優(yōu)化之后的ipa包大小是51.3MB,解壓縮包大小是90.8MB,Assets.car大小是469KB
調(diào)試符號
Build Settings-Generate Debug Symbols設(shè)置為NO;這個選項的意思是是否在源文件編程成.o文件時,添加編譯參數(shù)-g和-gmodule,就是generate complete debug info,所以產(chǎn)生的.o會變大,從而最終的可執(zhí)行文件也就會變大。
需要注意的是,如果設(shè)置為NO,在Xcode中設(shè)置斷點(diǎn)不會中斷,不能進(jìn)行斷點(diǎn)調(diào)試。且最后不能生成dSYM文件,即使Debug Infomation Format設(shè)置了,也無法生成,因為生成的前提是得有調(diào)試信息,建議不要設(shè)置。

無用符號
Build Settings-Deployment Postprocessing,調(diào)試模式下NO,發(fā)布模式下YES
Deployment Postprocessing是Strip的總開關(guān)。也就是說,只有Deployment Postprocessing這里設(shè)置了YES,Strip Debug Symbols During Copy和Strip Linked Product設(shè)置為YES才會生效,其余情況均不生效
Strip Linked Product:對最終的二進(jìn)制文件是否進(jìn)行去除無用符號Strip Debug Symbols During Copy:文件拷貝編譯階段是否進(jìn)行strip,設(shè)置為YES之后,會把拷貝進(jìn)項目的第三方庫、資源或者Extension的調(diào)試符號剝離

這個優(yōu)化之后,ipa壓縮包大小為51.4MB,解壓縮包大小變?yōu)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">90.9MB,其中exec變?yōu)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(244, 138, 0);">56.6MB,之前已經(jīng)開啟,所以無變化
第三個關(guān)于符號的優(yōu)化是Build Settings-Symbols Hidden by Default,調(diào)試模式下為NO,發(fā)布模式下為YES。該選項的意思是當(dāng)啟用時,所有的符號多被聲明為私有的extern,除非在代碼中使用attribute((visibility("default")))明確標(biāo)記為導(dǎo)出;反之,則所有符號都會被導(dǎo)出。

該測試項目中已開啟,包大小無變化
復(fù)用字符串
Build Settings-Make Strings Read-Only設(shè)置為YES;就是復(fù)用字符串字面。

該測試項目中已開啟,包大小無變化
無效代碼
Build Settings-Dead Code Stripping設(shè)置為YES;是否消除無用代碼

該測試項目中已開啟,包大小無變化
異常捕獲機(jī)制
Build Settings-搜索exceptions

如果想要對項目瘦身,需要對途中的綠色未知設(shè)置為NO
Enable C++ Exceptions和Enable Objective-C Exceptions是指項目對錯誤的異常處理,比如try catch, throw之類的語句;所以如果你的項目中有類似的處理,關(guān)閉這個之后會報錯Cannot use '@try' with Objective-C exceptions disabled。如果宏定義有try,關(guān)閉之后也會報錯

如果你的項目中可以設(shè)置關(guān)閉捕獲異常的開關(guān),還需要另外設(shè)置Build Settings-Other Link Flags去添加一個字段

-fno-exceptions代表禁用異常機(jī)制,參考這里:
http://gcc.gnu.org/onlinedocs/gcc-4.7.0/libstdc++/manual/manual/using_exceptions.html
現(xiàn)在來總結(jié)下編譯優(yōu)化
| 編譯選項 | _CodeSignature | Assets.car | embedded.mobileprovision | Info.plist | Plugins | Iproj | exec | 圖片資源 | others | ipa | 解壓縮包 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 初始配置 | 1.8MB | 518KB | 55KB | 16KB | 2.7MB | 2KB | 104.9MB | 16.7MB | 13.4MB | 76.5MB | 140.1MB |
Architectures | 1.8MB | 518KB | 55KB | 17KB | 1.8MB | 2KB | 56.6MB | 16.7MB | 12.6MB | 51.4MB | 90.1MB |
Optimization | 1.8MB | 469KB | 55KB | 17KB | 1.8MB | 2KB | 56.6MB | 16.7MB | 13.4MB | 51.3MB | 90.8MB |
Optimization Level | 1.8MB | 469KB | 55KB | 17KB | 1.8MB | 2KB | 53.6MB | 16.7MB | 13.5MB | 51MB | 87.9MB |
資源文件優(yōu)化
資源文件的優(yōu)化就相對比較簡單,但是比較繁瑣;與上面的Xcode編譯配置不同,一次設(shè)置永久有效,資源的優(yōu)化需要平時開發(fā)就需要關(guān)注,比如新資源的壓縮,無用資源的刪除等。
資源文件優(yōu)化大體分為兩個方向,第一個就是無用資源的刪除,第二個就是已用資源的壓縮。這里建議分先后順序,就是先做刪除,后做壓縮,因為如果反過來,就會做一些無用功。
無用資源的刪除
已定義未使用的代碼文件 已廢棄的業(yè)務(wù),代碼還在 已引用的圖片但未使用 某些重復(fù)資源的導(dǎo)入
已用資源的優(yōu)化
項目中引入的圖片、網(wǎng)頁、音頻等文件的壓縮
無用資源的刪除
隨著項目的迭代,每個項目都會或多或少存在冗余。可能是已經(jīng)下線的業(yè)務(wù),但是沒人通知開發(fā),于是代碼邏輯一直存在;可能是刪除某業(yè)務(wù)代碼的時候,對應(yīng)的圖片資源為刪除;又或者是多個開發(fā)導(dǎo)入了相同功能的第三方庫。
對于這部分的優(yōu)化分為預(yù)防和治理
關(guān)于預(yù)防
個人認(rèn)為首先要有規(guī)范的流程,按流程執(zhí)行,減少信息不對等的情況出現(xiàn)
產(chǎn)品和開發(fā),廣告和開發(fā),都會導(dǎo)致相關(guān)業(yè)務(wù)的冗余,產(chǎn)品知道具體的業(yè)務(wù)數(shù)據(jù),而開發(fā)不知道,所以通過某個固定時間的一次集中同步,讓開發(fā)可以了解到自己的業(yè)務(wù)是否活躍,從而對項目進(jìn)行相應(yīng)的調(diào)整。
開發(fā)與開發(fā)之間的消息不對等,會導(dǎo)致各自開發(fā)自己的輪子,重復(fù)造輪子,所以可以通過建立公共文檔、開發(fā)流程規(guī)范、項目使用第三方庫規(guī)范、設(shè)計規(guī)范、代碼規(guī)范都一一列舉出來,每個人都能根據(jù)對應(yīng)的文檔了解到對應(yīng)項目的信息,每個人開發(fā)都應(yīng)該有一套統(tǒng)一的標(biāo)準(zhǔn),這樣就很大程度上避免了一人一套代碼問題的出現(xiàn)。
具體規(guī)范流程可以根據(jù)自己公司或自己部分的實(shí)際情況來,多問為什么,比如這樣
為什么會有類似的情況出現(xiàn)? 出現(xiàn)之后是否有處理? 怎么才能避免類似的事再次出現(xiàn)?
關(guān)于治理
已定義未使用的代碼
如果你的項目代碼在百萬級以下,推薦你使用AppCode來靜態(tài)檢查無用代碼,如果超過了百萬的團(tuán)隊,一般會自己通過Clang靜態(tài)分析來開發(fā)工具,去檢查無用的代碼和類
使用AppCode做分析很簡單,直接使用AppCode打開你需要檢查的工程,等待索引建立完成,點(diǎn)擊頂部菜單的Code-Inspect Code

然后選擇掃描范圍,默認(rèn)是整個工程,可以自定義

然后等待靜態(tài)分析完成即可,完成之后就會控制臺顯示Unused code的提示

下面說下無用代碼的類型
| 內(nèi)容 | 中文翻譯 |
|---|---|
Unused class | 無用類 |
Unused global declaration | 無用的全局聲明 |
Unused import statement | 無用的類引入聲明 |
Unsed instance variable | 無用的實(shí)例變量 |
Unused local variable | 無用的局部變量 |
Unused macro | 無用的宏 |
Unused method | 無用的方法 |
Unused parameter | 無用的參數(shù) |
Unused property | 無用的屬性 |
Unused value | 無用的值 |
雖然AppCode完成了大部分的工作,但是還是有一些問題存在,下面是我測試總結(jié)的問題
JSONModel定義了未使用協(xié)議會被判定為無用協(xié)議如果子類使用了父類的方法,父類的這個方法會判斷為未使用 通過點(diǎn)語法使用屬性,該屬性會被認(rèn)為未使用 使用 performSelector方式調(diào)用的方法檢查不出來,比如[self performSelector:@selector(fetchPhotos:)],會認(rèn)為fetchPhotos:是未使用運(yùn)行時聲明的類也檢查不出來,比如通過 NSClassFromString方式調(diào)用的類會被查出未使用
id?someClass?=?NSClassFromString(@"SomeClass");
//?or
[[self?class]?getPhotos];
基于上述的原因,我們需要人工二次確認(rèn)才能夠安全刪除代碼,雖然繁瑣,但是很安全。
已廢棄業(yè)務(wù),代碼還在
定期對業(yè)務(wù)流程進(jìn)行梳理,結(jié)合綜合業(yè)務(wù)數(shù)據(jù)的埋點(diǎn),同產(chǎn)品和廣告確認(rèn)功能是否已經(jīng)下線,從而決定是否移除對應(yīng)的業(yè)務(wù)模塊
已引入未使用的圖片
這里我測試使用的是LSUnusedResources(https://github.com/tinymind/LSUnusedResources),針對使用編號規(guī)則的圖片來說,可以直接添加規(guī)則來處理,使用也很簡單;建議在刪除前在項目中進(jìn)行二次全局搜索確認(rèn),是否確實(shí)沒有使用

某些重復(fù)資源的導(dǎo)入
重復(fù)資源分為靜態(tài)庫和項目文件
針對靜態(tài)庫,有多個相似功能進(jìn)行需求整合,把最優(yōu)解決方案的靜態(tài)庫留下,其余的跟相關(guān)產(chǎn)品和廣告人員進(jìn)行移除確認(rèn),比如線上崩潰采集聽云、bugly和matrix等
針對項目文件,可以使用fdupes工具進(jìn)行重復(fù)文件掃描,該工具的原理是通過校驗所有的資源的MD5值,篩選出項目中重復(fù)的資源,文件比較順序是
文件大小 > 部分 MD5簽名對比 > 完整MD5簽名對比 > 逐字節(jié)對比
fdupes的安裝和使用
#?Searches?the?given?path?for?duplicate?files.?Such?files?are?found?by
#?comparing?file?sizes?and?MD5?signatures,?followed?by?a?byte-by-byte?comparison.
#?install?fdupes
$?brew?install?fdupes
#?where?xxx?is?the?directory?to?be?scanned,?and?xxxFdupesResult.txt?is?the?output?file?of?the?scan?result
$?fdupes?-Sr?/User/augus/Documents/xxx?>?/User/augus/Documents/xxxFdupesResult.txt
已用資源的優(yōu)化
已用資源的優(yōu)化主要是就是圖片、網(wǎng)頁、json、音頻文件的壓縮
網(wǎng)頁的壓縮,是放入App資源中的js或者html文件,最好是經(jīng)過?H5端壓縮后的
json文件的壓縮,如果不是即時使用的,可以放倒云端或者后臺,進(jìn)行網(wǎng)絡(luò)獲取后使用
音頻的壓縮,需要跟產(chǎn)品和廣告進(jìn)行溝通,在可接受范圍內(nèi),選擇系統(tǒng)可支持的壓縮比最高的格式
最后說圖片的壓縮,分為以下幾個部分
如果圖片小于
100KB則使用TinyPng進(jìn)行再一步壓縮,使用TinyPng壓縮三次即可達(dá)到極限值;這里推薦一個TinyPng的多圖壓縮的腳本(https://github.com/mokong/BatchProcessImage),因為在線最多一次最多20張,原理就是封裝了官方的一個壓縮接口,通過一個郵箱注冊一個key,然后每個key就是500次/月。將圖片放入
xcassets,因為xcassets里的@2x和@3x圖片,在上傳時,會根據(jù)具體設(shè)備分開對應(yīng)分辨率的圖片,不會同時包含。而放入.bundle中的都會包含,所以要盡量把圖片放入xcassets中。Assets.car編譯過程中有時會選擇一些圖片,拼湊成一張大圖來提高圖片的加載效率。被放進(jìn)這張大圖的小圖會變?yōu)橥ㄟ^偏移量的引用,建議使用頻率高且小的圖片放到xcassets中,xcassets能保證加載和渲染速度最優(yōu)。大于
100KB就不要放入xcassets中了。大的圖片可以考慮將圖片轉(zhuǎn)成WebP。WebP是Google公司的一個開源項目,能夠把圖片壓縮到很小,但是肉眼看不出來差別,目前iOS常用的圖片顯示類庫都支持該格式解析的拓展。可使用鵝廠的一個工具iSparta(http://isparta.github.io/)進(jìn)行批量轉(zhuǎn)換。
WebP在CPU消耗和解碼時間上會比PNG高2倍,所以我們要在性能和體積上做取舍
監(jiān)控機(jī)制
優(yōu)化完成之后,如何保證包大小不會再次迅速增大?或者如何迅速定位增大原因,就需要依賴一套合適的監(jiān)控機(jī)制和合理的流程規(guī)范來保證。
監(jiān)控機(jī)制保證實(shí)時發(fā)現(xiàn)問題,每次打包完成后,運(yùn)行腳本比較包大小差異,如果有增大超過了設(shè)置的閾值,則郵件通知相關(guān)負(fù)責(zé)人,負(fù)責(zé)人排查原因,同時做好記錄;每次打包的包大小進(jìn)行文檔記錄,造成包大小變化的原因記錄
流程規(guī)范是用于保證每個項目開發(fā)者在開發(fā)中有良好的開發(fā)習(xí)慣,避免包大小突然增大,而一無所知
引入新的第三方庫,要考慮項目中是否有相同類型的,是否可以自己實(shí)現(xiàn),是否會造成體積增大 新增圖片資源,關(guān)注大小,是否能用代碼實(shí)現(xiàn),進(jìn)行適當(dāng)壓縮 及時清理廢棄模塊 每次打包關(guān)注包大小變化,文檔記錄,持續(xù)跟蹤
結(jié)束語
包大小不是一時可以完成,需要平時每個相關(guān)人都去注意,有了良好的開發(fā)規(guī)范,開發(fā)流程就能持續(xù)進(jìn)行改進(jìn),從而達(dá)到一個平衡穩(wěn)定狀態(tài)。
參考文檔
Build Setting Reference https://help.apple.com/xcode/mac/11.4/#/itcaec37c2a6 包體積大小:瘦身 https://www.zybuluo.com/qidiandasheng/note/1662385) 抖音品質(zhì)建設(shè) - iOS 安裝包大小優(yōu)化實(shí)踐篇 https://blog.csdn.net/ByteDanceTech/article/details/112504772
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?面試題?資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取