iOS安裝包瘦身小記

南華coder發表於2019-04-20

閒散心如月,風光好自知

原文連結

一、安裝包組成分析

1、組成情況

​ 將IPA包修改字尾名為ZIP,解壓縮後,獲取payload中的App檔案,檢視App檔案的內容,你會發現該檔案主要包含以下內容

  • Exectutable: 可執行檔案
  • Resources:資原始檔
    • 圖片資源:Assets.car/bundle/png/jpg 等
    • 視訊/音訊資源:mp4/mp3 等
    • 靜態網頁資源:html/css/js 等
    • 檢視資源:xib/storyboard 等
    • 其他:文字/字型/證書 等
  • Framework
    • SwiftSupport: libSwiftxxx 等一系列 Swift 庫
    • 其他依賴庫:Embeded Framework
  • Pulgins:Application Extensions
    • appex:其組成大致與 ipa 包組成一致
2、組成分析
  • 一般來說,可執行檔案、圖片資源(asset.car)和動態庫的佔比最大,如果是Swift和OC混編,可執行檔案比純OC大很多
  • 從優化的效果上看,優化圖片資源的ROI比較大,如果是首次優化,建議從圖片資源的優化開始。
  • 專案中使用Swift,會增加安裝包大小,因為FrameWork中會加入為了支援 Swift 的動態庫集合,如果純Swift專案,不會引入這些東西。

二、資原始檔優化

​ 理論上,資原始檔包括:圖片**、視訊、**音訊和字型等;實際上,視訊和音訊檔案一般不會整合到安裝包中,在安裝包中的資原始檔主要是圖片。

1、優化手段1:App Slicing
  • iOS 9之後提供了App Thinning三件套:App SlicingOn Demand ResoucesBitcode
App Thinning 理想 現實
App Slicing 將App Bundle資源根據不同的裝置特性分為不同的版本。對於圖片資源,會根據裝置所需圖片解析度不同分發給對應裝置所需對應的圖片資源。 主要是圖片資源的Slicing,我們有自己的方案,沒有采用
On Demand Resources App的資源只有要使用時才下載,如果其他資源需要空間這些資源可以被移除 更適合遊戲類App,專案沒有使用
Bitcode Bitcode可以作為中間產物一起提交AppStore。包含Bitcode配置的程式將會在AppStore上被編譯和連結。Bitcode允許蘋果在後期重新優化我們程式的二進位制檔案,而不需要我們重新提交一個新的版本到AppStore上 使用BitCode的要求所有程式碼都支援BitCode,改動專案較大,沒有使用

說明:可以充分利用App Slicing實現圖片資源的瘦身

  • 在專案中引入圖片時候,直接在 Assets.xcassets中新增就可以(資原始檔用Asset Catalog管理),這樣能使用到App Slicing功能,這樣當使用者從App Store上下載App時,可以只下載適用於其裝置的App架構版本和所需資源,從而減少App所佔的空間。
  • 在實踐中發現,有的新同學在Assets.xcassets中引入@1x的圖片,iPhone手機目前需要的@2x和@3x圖片,所以@1x的圖片顯然是不需要的。
  • 在實踐中還發現,有的圖片資源遊離在Assets.xcassets之外,這些可以考慮是否可以放入Assets.xcassets中(大部分情況下是可以放入的)
2、優化手段2:Xcode編譯項
  • 因為絕大部分引入的圖片是PNG格式,Xcode 提供的給我們兩個編譯選項來幫助壓縮 PNG 資源:

  • Compress PNG Files:設定為YES,打包的時候自動對圖片進行無失真壓縮,使用的工具為 pngcrush,壓縮比還是相當高的,比較流行的壓縮軟體 ImageOptim 也是使用 pngcrush 進行壓縮 PNG 的。

  • Remove Text Medadata From PNG Files:設定為YES,能幫助我們移除 PNG 資源的文字字元,比如影像名稱、作者、版權、創作時間、註釋等資訊。

  • 引入專案的PNG資源自動被 Xcode 進行壓縮了,但是如果是使用Bundle管理的資源,不會被Xcode壓縮,可以使用tinypng壓縮。

3、優化手段3:清理無用的資源
  • 及時清理不使用的圖片資源。使用類似LSUnusedResources 清理舊的圖片檔案。
  • LSUnusedResources的思路是,先獲取圖片檔案(imageset, jpg, png, gif)集合A,然後搜尋程式碼檔案中所有字串名稱得到B,然後從A集合中排除集合B就得到未使用的圖片資源。
4、優化手段4:圖片檔案去重
  • 遍歷圖片檔案,計算每個檔案的MD5值,然後以MD5值為key,檔案路徑存入key對應的陣列;
  • 遍歷字典values,將value的陣列大小大於1的路徑輸出,這樣就找到重複圖片的路徑了。
5、優化手段5:更適合的圖片格式
  • iconfont代替專案中純色小圖示,也省去很多@2x和@3x的圖片切圖。
  • PNG切圖的替換方案,如PDF向量圖來代替大部分簡單的png切圖;然後在程式碼中自己解碼並展示出來,一套PDF向量圖可以等效大部分2x和3x的png圖片;
  • 網路圖片選擇壓縮比更好的圖片格式,如webp

說明:PNG切圖不可能被完全替換,在表現顏色豐富圖片時候,PNG效果很不錯,其他詳見淺談iOS圖片優化

三、可執行檔案優化

1、優化手段1:編譯器優化
  • Xcode 支援編譯器層面的一些優化優化選項,可以讓我們介於更快的編譯速度更小的二進位制大小更快的執行速度之間自由選擇想要進行的優化粒度;

  • 在Xcode中,使用Clang來編譯Objective-C,可以在 Build Setting -> Apple Clang - Code Generation -> Optimization Level 設定,Release下為Fastest Smallest[-Os]。編譯器會開啟除了會明顯增加包大小以外的所有優化選項;

  • 在Xcode中,使用SwiftLang來編譯Swift語言,同樣也是基於 LLVM 後端的。Xcode 9.3 版本之後可以在Build Setting -> Optimization Level 設定,Release下為Optimize for Speed[-O],這可能會增加安裝包大小

No optimization[-Onone]:不進行優化,能保證較快的編譯速度。
Optimize for Speed[-O]:編譯器將會對程式碼的執行效率進行優化,一定程度上會增加包大小。
Optimize for Size[-Osize]:編譯器會盡可能減少包的大小並且最小限度影響程式碼的執行效率
複製程式碼

說明:Xcode 9.3/Swift4.1編譯器不是特別穩定,特別是開啟 Osize 選項之後,編譯器很多情況下會莫名其妙的崩潰(Segmentation fault),目前放棄 [-Osize],選擇[-O]

2、優化手段2:去除符號資訊
  • 可執行檔案中的符號:程式中的所有的變數、類、函式、列舉、變數和地址對映關係,以及一些在除錯的時候使用到的用於定位程式碼在原始碼中的位置的除錯符號,符號和斷點定位以及堆疊符號化有很重要的關係。

  • Strip Style表示的是我們需要去除的符號的型別的選項,可以在Build Setting -> Strip Style設定, Release下為All Symbols,其分為三個選擇項:

All Symbols: 去除所有符號,一般是在主工程中開啟。

Non-Global Symbols: 去除一些非全域性的 Symbol(保留全域性符號,Debug Symbols 同樣會被去除),連結時會被重定向的那些符號不會被去除,此選項是靜態庫/動態庫的建議選項。

Debug Symbols: 去除除錯符號,去除之後將無法斷點除錯。
複製程式碼

說明:iOS 的除錯符號是 DWARF 格式的,使用 Xcode 編譯打包的時候會先通過可執行檔案的 Debug Map 獲取到所有物件檔案的位置,然後使用 dysmutil 來將物件檔案中的 DWARF 提取出來生成 dSYM 檔案。

  • Strip Linked Product去除不必要的符號資訊,去除了符號資訊之後我們就只能使用 dSYM 來進行符號化了,所以需要將 Debug Information Format 修改為 DWARF with dSYM file。Release下為YES。

  • Strip Linked Product 選項在 Deployment Postprocessing 設定為 YES 的時候才生效,而在 Archive 的時候 Xcode 總是會把 Deployment Postprocessing 設定為 YES,Debug下,Deployment Postprocessing 設定為 NO。

  • Strip Debug Symbols During Copy將那些拷貝進專案包的三方庫、資源或者 Extension 的 Debug Symbol 去除掉,在Build Settings -> Strip Debug Symbols During Copy設定,Release下設定為YES。

  • Cocoapods 管理的動態庫(use_framework!)的情況就相對要特殊一點,因為 Cocoapods 中的的動態庫是使用自己實現的指令碼 Pods-xxx-frameworks.sh 來實現拷貝的,所以並不會走 Xcode 的流程,當然也就不受 Strip Debug Symbols During Copy 的影響。當然 Cocoapods 是原始碼管理的,所以只需要將原始碼 Target 中的 Strip Linked Product 設定為 YES 即可。

  • Strip Swift Symbols能幫助我們移除相應 Target 中的所有的 Swift 符號,這個選項也是預設開啟的。Strip Swift symbols需要在打包的釋出選項中勾選(預設勾選),在Swift ABI 穩定之前,Swift 標準庫是會打進目標檔案的。

3、優化手段3:BitCode
  • Bitcode可以作為中間產物一起提交AppStore。包含Bitcode配置的程式將會在AppStore上被編譯和連結。Bitcode允許蘋果在後期重新優化我們程式的二進位制檔案,而不需要我們重新提交一個新的版本到AppStore上。

  • 開啟 BitCode 之後編譯器後端(Backend)的工作都由 Apple 接管了。所以假如以後蘋果推出了新的 CPU 架構或者以後 LLVM 推出了一系列優化,我們也不再需要為其釋出新的安裝包了。

  • 工程開啟 BitCode 之後必須要求所有打進 Bundle 的 Binary 都需要支援 BitCode,也就是說我們依賴的靜態庫和動態庫都是含有 BitCode 的,不然就會打包失敗。對於 Cocoapods 等原始碼管理工具來管理的依賴庫來說操作會比較簡單,我們只需要開啟 Pods 工程中的 BitCode 就行。但是對於一些三方的閉源庫,我們就無能為力了。

  • 開啟 BitCode 之後,由於最終的可執行檔案是 Apple 自動生成的,同時產生新的符號表檔案,所以我們使用原本打包生成的 dSYM 符號化檔案是無法完成符號化的。所以我們需要在上傳至 App Store 時需要勾選 Include app symbols for your application to receive symboilcated crash logs from Apple:勾選之後 Apple 會給我們生成 dSYM,然後就可以在 Xcode -> Organizer 或者 iTunes Connect 中下載對應的 dSYM 來進行符號化了。

4、優化手段4:清除無用程式碼
  • Dead Code Stripping:Xcode 預設會開啟此選項,C/C++/Swift 等靜態語言編譯器會在 link 的時候移除未使用的程式碼,但是對於 Objective-C 等動態語言是無效的。因為 Objective-C 是建立在執行時上面的,底層暴露給編譯器的都是 Runtime 原始碼編譯結果,所有的部分應該都是會被判別為有效程式碼。

  • 掃描查詢無用程式碼:基本思路都是查詢已經使用的方法/類和所有的類/方法,然後從所有的類/方法當中剔除已經使用的方法/類剩下的基本都是無用的類/方法,但是由於 Objective-C 是動態語言,可以使用字串來呼叫類和方法,所以檢查結果一般都不是特別準確,需要二次確認。目前市面上的掃描的思路大致可以分為 3 種:

    • 基於 Clang 掃描
    • 基於可執行檔案掃描
    • 基於原始碼掃描
  • 及時下線不需要的功能,如完成使命的ABTest程式碼、被產品拋棄的功能程式碼等。

  • 移除不需要的系統庫和第三方庫。

5、優化手段5:程式碼重構
  • 功能合併:相似功能的程式碼,只需維護一份就可以了。如定製通用UI元件,大家可以有類似需求,可以給通用UI元件的開發提,沒必要自己單獨實現。

End

1、優化之後
  • 保持良好的開發習慣。及時清理無用程式碼和無效庫

  • 持續關注安裝包大小的變化,

  • 定期Review安裝包大小變化

  • 建議預警機制,監控每個版本的體積大小,體積圖片突然變大,要去找原因。

2、參考資料

iOS App 瘦身實踐總結

iOS 安裝包瘦身(上篇)

iOS 安裝包瘦身(下篇)

相關文章