背景知識
App的大小分為下載大小和安裝大小。
下載大小指的是App壓縮包(.ipa)所占的空間大小。用戶下載的是壓縮包,下載完成后會自動解壓,安裝大小就是指壓縮包解壓后的大小。
下載大小如果超過200MB,iOS13以下無法通過蜂窩網絡下載;iOS13以上會通過用戶手動設置才可以下載。
包大小優化
網上有很多包大小優化的方案,我看了很多博客,根據博客總結一下包大小優化相關的操作,如下圖。
Xcode編譯設置
- 去掉異常支持,Build Setting中Enable C++ Exceptions和Enable Objective-C Exceptions設置為NO,Other C Flags添加-fno-exceptions
注意??:Enable C++ Excptions和Enable Objective-C Exceptions是指項目支持異常處理,關閉后try cache、throw、包括宏定義try{}、@finally{}、@strongify會報錯。
-fno-exceptions的意思是禁用異常機制,參考gcc,同樣,當項目中有try thorw的時候,就不要設置這個選項為NO。
Before detailing the library support for -fno-exceptions, first a passing note on the things lost when this flag is used: it will break exceptions trying to pass through code compiled with -fno-exceptions whether or not that code has any try or catch constructs. If you might have some code that throws, you shouldn't use -fno-exceptions. If you have some code that uses try or catch, you shouldn't use -fno-exceptions.
2.Build Settings -> Architectures,在Excluded Architectures中設置Release模式下 Any iOS SDK -> armv7,設置之后在Release下把armv7排除。
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 x86_64: 模擬器64位處理器 i386: 模擬器32位處理器
這里:iOS9之后推出了App Thinning,在這過程中蘋果已經幫忙去掉armv7了。
3.Build Settings -> Generate Debug Symbols設置為NO
Generate Debug Symbols是生成調試符號,斷點不能用,最后不能生成DSYM文件(DSYM文件是指具有調試信息的目標文件)。建議不要設置為NO。
- Build Settings -> Deployment Postprocessing,Debug模式下設置NO,Release下設為YES
Deployment Postprocessing是Strip配置的總開關
Build Settings -> Strip Linked Product,Debug下設置為NO,Release下設置為YES。
設置為Yes時,對最后的二進制文件進行strip,去除不必要的符號信息。
注意??:去除了符號信息之后需要使用dSYM來進行符號化,所以需要將 Debug Information Format 修改為DWARF with dSYM file(Release下),如果在Debug下設置為DWARF with dSYM file那么在崩潰時將無法看到堆棧信息。
Build Settings -> Strip Debug Symbols During Copy,Debug下設置為NO,Release下設置為YES
文件拷貝編譯階段是否進行strip,設置為YES之后,會把拷貝進項目包的三方庫、資源或者Extension的Debug Symbol去除。
5.Build Settings -> Symbols Hidden by Default,Debug模式下設置為NO,Release下設置為YES
Symbols Hidden by Default會把所有符號都定義成"private extern",移除符號信息
6.Build Settings -> Make Strings Read-Only設置為YES
復用字符串字面量
7.Build Settings -> Dead Code Stripping設置為YES
消除無效代碼,C/C++/Swift 等靜態語言編譯器會在 link 的時候移除未使用的代碼,對于OC等動態語言是無效的
- Asset Catalog Compiler編譯設置優化,Build Settings ->
Compiler - Options 中Optimization改為space
這個選項可以改變actool在構建Assets.car時選取的編碼壓縮算法,減少包大小
9.Build Settins -> Optimization Level改為-Oz
Optimization Level默認為-Os,-Oz是Xcode 11之后才出現的編譯優化選項,核心原理是對重復的連續機器指令外聯成函數進行復用,因此開啟Oz,能減少二進制的大小,但同時會帶來執行效率但額外消耗。
Optimization Level各參數優化的選擇對比,如下圖,對于性能要求高的,建議選擇-O2和-O3,對于包大小敏感的,可選擇-Os和-Oz,默認-Os是性能和大小平衡比較好的。最終選擇什么,需要讀者根據自己實際項目而定。
Pod設置
在OC的項目中,Podfile如果引用了Swift的第三方庫,一般都會直接打開use_frameworks!,對應的Pod中所有的庫都會
打包成動態庫,以及Swift和OC庫的依賴問題會導致依賴庫增加,會造成包體積增大。所以我們可以有優化為針對單個Swift庫使用use_frameworks!
我按照上述修改后,pod install報錯了,修改方法如下圖。
資源優化
刪除未使用的類
查看了一些文檔后,我使用了python腳本的方法來查找項目中未使用的類。腳本地址如下:
腳本地址
執行方法:
1.cd到腳本所在文件夾中
2.運行下需要檢測的項目后按照如下步驟找到.app文件所在文件夾
3.執行python腳本
python FindClassUnRefs.py -p /Users/a58/Library/Developer/Xcode/DerivedData/XXX-bqqoxganvkvgwuefbskxsbvnxlnn/Build/Products/Debug-iphonesimulator/XXX.app -w JD,BD,AL
參數說明:
-p Xcode運行之后的,項目Product路徑
-w 結果白名單處理,檢測結果,只想要以什么開頭的類,多個用逗號隔開,比如JD,BD,AL
-b 結果黑名單處理,檢測結果,不想要以什么開頭的類,多個用逗號隔開,比如Pod,AF,SD
-w 和 -b 不能共存,共存會報錯
用這個方法并未查找到未使用的category,并且還是需要在項目中進行搜索判斷是否使用該類,結果并不完全準確。
刪除未使用圖片
通過工具LSUnusedResources,運行后,輸入項目文件位置,即可查找未使用的圖片。
LSUnusedResources下載地址
原理大致是遍歷資源目錄下后綴 ["imageset", "jpg", "png"...] 的文件,然后在源文件 ["m", "swift", "xib", "storyboard"...] 中字符串匹配,無匹配則是無用的資源文件。
使用時注意勾選Ignore similar name,然后點擊右上角的Browse選中要掃描的項目地址,點擊右下角的search,就會開始掃描,結果會在底部Unused Results中展示出來,然后CMD+A全選,export,導出到一個文本文件中。也可以在對應單條Item上面雙擊,會打開對應的文件夾。建議刪除前在項目中搜索確認,是否確實沒有使用(類似字符串中間替換的可能會被掃描出來,所以刪除前需要確認)
壓縮圖片
可以通過以下網站壓縮圖片
tinyPNG
總結:
以上就是我包大小優化的過程,查看了一些博客,然后根據博客上所說的進行修改。以下是我打包兩個.ipa包的大小對照圖,從32M優化到了19.8M。
Mach-O文件
我們講.ipa文件后綴修改為.zip后解壓,會看到文件中所包含的內容。主要有codeSignature、Assets.car、還有一個exe文件,這個文件的格式是Mach-O。
在這里也可以看到nib文件,因為項目中使用xib文件繪制UI。由此可以知道,如果想要控制包大小,最好不要使用xib文件(個人理解)。
可以使用MachView來查看Mach-O文件,MachView下載地址如下
MachView下載地址
Mach-O文件主要包括三部分:
- Header:文件基本屬性、CPU的類型、Load Commands的個數等。
- Load Commands:由多條Load Command組成,它們描述了Data在二進制文件和虛擬內存中布局,有了這個布局就能夠知道Data的排布情況。
- Data:存儲的實際內容,主要是程序的指令和數據,按照Load Commands描述排布。
使用以下指令可以查看Mach-O文件Data部分的結構和大小信息。
xcrun size -lm /Users/boyankeji/Downloads/LianCheng\ 2022-08-09\ 16-43-21/Payload/連成物聯.app/連成物聯
如圖所示,主要包括了4部分:
- __PAGEZERO
- __TEXT
- __DATA
- __LINKEDIT
__PAGEZERO在內存中不可讀不可寫,用于捕獲NULL指針。它并不占用Data的空間。
__TEXT、__DATA保存代碼的指令和數據。
__LINKEDIT包含啟動App需要的信息。
在今日頭條優化實踐: iOS 包大小二進制優化,一行代碼減少 60 MB 下載大小這個博客中的包大小優化方法,主要就是將__TEXT段進行遷移。
其中原理是:
When your app is approved for the App Store, it is encrypted with DRM and recompressed. The added encryption and DRM affects the ability to compress your binary, and as a result you may see a larger App Store file size for your binary than the binary you uploaded on App Store Connect. The exact final size for your app cannot be determined in advance to the accuracy of a single byte.
我們將項目進行Archive后生成.xcarchive文件,文件上傳到App Store Connect后,蘋果會對可執行文件進行加密,然后再將App壓縮成.ipa文件,然后發布到App Store。
加密會影響可執行文件的壓縮效率,從而導致ipa大小增加,而這種加密效果幾乎沒用。
Mach-O 文件代碼的解密發生在 Mach-O 文件被加載的時候,由 Mach Loader 進行。Mach Loader 會讀取 Mach-O 中的 LC_ENCRYPTION_INFO 這條 Load Command 來判斷可執行文件是否加密。
cryptid 表示加密方法為 1,如果為 0 表示不加密。
通過以下指令可以查看Load Commands
otool -l /Users/boyankeji/Downloads/LianCheng\ 2022-08-09\ 16-43-21/Payload/連成物聯.app/連成物聯
我們通過查看__TEXT的filesize,通過計算可以知道加密的內容主要在__TEXT上。所以我們將__TEXT移走可以達到減少包大小的目的。
注意??:
蘋果在 iOS 13 已經對下載大小做了優化,所以本方案無法再對 iOS 13 的設備的下載大小進一步優化。
即,若用戶的設備 < iOS 13,那么本方案可以減少該設備上 App 32~34%的下載大??;
若用戶的設備 >= iOS 13,本方案不會對該設備的 App 的下載大小有進一步優化,也不會有負面影響。
包大小優化總結
根據查找的博客來看,包大小優化主要是三部分:
- Xcode中刪除編譯需要的Symbols、Debug符號等;
- 對資源的控制,不要重復引入圖片、過大的資源文件需要進行壓縮;未用的代碼、類需要及時刪除;
- 黑科技,根據頭條給出的方案,遷移__TEXT
以上是我根據最近一段時間的學習總結的,在學習的工程中,會發現自己對程序的構建、腳本的使用、Mach-O文件等都不太了解,現在通過學習感覺也只是皮毛的階段。以后如果有時間需要更加深度的學習。
通過學習包大小優化的內容,以后在開發的過程中需要增加包大小優化的意識。
學習鏈接
App Store上的包大小
分析iOS包大小優化
iOS 腳本查看項目中未使用的類
iOS檢測項目中無用類和方法
今日頭條優化實踐: iOS 包大小二進制優化,一行代碼減少 60 MB 下載大小
iOS底層探索- Mach-O文件