背景
包大小優化是項目開發中不可避免會遇到的,網上關于包大小優化的文章很多,每篇文章說的都不盡相同,筆者曾經根據網上的文章做過包大小優化,但效果不盡人意;因此筆者想根據已有的文章、知識結合自己的理解、實踐,做一份總結梳理,整理自己的包大小優化邏輯,不光要知道怎么做可以讓包大小變化,還要知道為什么這么做能產生效果,所以就有了這篇文章。
分析
想要優化安裝包大小,首先需要弄清楚影響安裝包大小的因素有哪些?之前筆者優化包大小直接悶著頭就去瘦身,瘦來瘦去也沒瘦出個名堂,而且還跟別人說不清楚自己做了什么,為什么這么做?
后來總結出來,做事之前要先思考、分析、最后再去做,要思考的是影響這件事的因素有哪些,一一列舉出來,查漏補缺;然后針對這些因素進行分析,分析哪些因素是人為可控的,哪些因素是不能改變的,針對可控的部分要怎么優化,不可控的部分是否能避免,最好可以使用思維導圖工具,一一記錄;然后按照思維導圖的整理出來的數據,按步驟去執行。
回過頭來,針對安裝包大小,首先分析影響安裝包大小的因素,有:Xcode的設置、資源、代碼三個方面。那針對這幾個方面要怎么優化?以及如何查看每一步優化的結果?
首先是怎么優化的問題:
Xcode的編譯設置優化,Xcode設置影響的是生成包的大小,通過Xcode編譯選項優化的設置,讓生成的ipa包變小,比如不含斷點調試、去掉異常支持等等。
資源文件的優化,資源不光有圖片資源,也包含代碼資源和其它導入的資源,可以通過分析安裝包構成,看里面哪些部分比較大、不合理,從而進行優化。
代碼的優化,通過Link Map生成Link Map File,分析Link Map File各文件占用,結合Mach-O文件進行分析優化。
然后是怎么查看每一步優化的結果的問題:
查看每一步的優化結果,可以通過分析打包出來的ipa的大小,以及ipa的組成,與初始的ipa包大小比較,即可直觀得到優化的結果。但可以更進一步,分析ipa的構成,對比優化后的構成,看每一步的操作具體影響的是包的哪一塊兒,從而導致包的大小發生了變化。所以先來看一下一個ipa的包包含哪些內容,然后每一步優化之后,對應ipa的哪一部分發生了變化?
安裝包的構成
iOS打包出來的ipa,本質上是一個壓縮包,所以可以將.ipa的后綴改為.zip,然后進行解壓縮,之后會得到一個Payload文件夾,里面又一個xxx.app的文件,這個xxx.app就是包含所有文件的包了,選中xxx.app,右鍵顯示包內容,即可看到里面具體包含的東西了,大致如下:
安裝包:
_CodeSignature:ipa包簽名文件
.lproj: 語言文件
Frameworks: 第三庫、SwiftSupport庫
Plugins: App創建的擴展,比如:Widget、Push、分享
Assets.car: 由Assets.xcassets生成的資源文件,里面包含各種分辨率的圖片
embedded.mobileprovision:證書配置文件
Info.plist: 項目配置
exec格式的xxx: 可執行包
其它資源文件
.mp3格式的文件
.html的文件
.json的文件
.png或者.jpg的文件
示例如下,Ps:不得不說,筆者這個ipa包含的內容真的是很全面了,各種的資源都有,哈哈哈
筆者初始ipa包大小為22.9M,解壓縮之后.app的大小為57.1M,其中各部分大小明細如下
內容 | 大小 |
---|---|
_CodeSignature | 93 KB |
.lproj | 4 KB |
Frameworks | 37.5 MB |
Plugins | 181 KB |
Assets.car | 4.9M |
embedded.mobileprovision | 8KB |
Info.plist | 6 KB |
exec格式的xxx | 13 MB |
其它資源文件 | 1.4 MB |
了解了iPa包的組成之后,我們再回過頭來,按照Xcode的編譯優化、資源文件優化、代碼優化的步驟來一步步分析。
Xcode編譯設置
一般這一步容易被人忽略,因為提到優化最先能想到的就是資源優化,比如圖片壓縮、無用代碼刪除等等,而對于Xcode自身的編譯優化提及的反而不多。而且由于網上提供的參考針對每個項目可能結果都不一樣,有些編譯選項的設置是需要針與實際項目結合起來才可以,所以筆者這里整理和總結了一下:
Xcode編譯優化相關:
-
Build Settings中去掉異常支持,Enable C++ Exceptions和Enable Objective-C Exceptions設置為NO,Other C Flags添加-fno-exceptions;
圖片
圖片注意:Enable C++ Excptions和Enable Objective-C Exceptions是指項目支持對錯誤的異常處理,比如try catch、throw之類的;所以如果項目中使用的有類似的異常處理的,這個關閉了之后會報錯(Cannot use '@try' with Objective-C exceptions disabled)。包括宏定義中使用的有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.
-
Build Settings -> Architectures,Release下設置為arm64
圖片Architectures指定工程被編譯成可支持哪些指令集類型,支持的指令集越多,就會編譯出多個指令集代碼的數據包,ipa包就會變大。默認的standard architectures(armv7,arm64) 參數,打的包里面有32位、64位兩份指令集。如果不需要32位的,可以在other中更改支持的指令集,從而使ipa包變小。
?
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位處理器注意:Xcode 12之后,沒有了Valid Architectures選項的設置。另:如果把Architectures從standard architectures(armv7,arm64)改為arm64,則會發現target無法選擇模擬器運行了,所以建議Debug模式下修改為arm64、x86_64或者不修改,Release下設置為arm64。
更新:經朋友指點,這個地方Architectures的設置,還有另外一種設置方法,Architectures不修改,Excluded Architectures中設置Release模式下 Any iOS SDK -> armv7,也可以實現同樣的效果。設置了之后,就是Release下把armv7的指令集排除在外。選中target會發現默認設置了 Any iOS Simulator SDK -> arm64,意思是模擬器的時候排除arm64指令集。
如下:
圖片 -
Build Settings -> Generate Debug Symbols設置為NO
圖片Generate Debug Symbols的意思是生成調試符號,當這個選項設置為YES時,每個源文件在編譯成.o文件時,編譯參數多了-g和-gmodule,意思是generate complete debug info,所以產生的.o文件會大,從而最終生成的可執行文件也就會變大。
?
Generate Debug Symbols (GCC_GENERATE_DEBUGGING_SYMBOLS), Enables or disables generation of debug symbols. When debug symbols are enabled, the level of detail can be controlled by the Debug Information Format (DEBUG_INFORMATION_FORMAT) setting.
注意Generate Debug Symbols設置為NO時,在Xcode中設置的斷點不會中斷,即不能斷點調試。且最后不能生成DSYM文件,即使Debug Information Format設置了,也不能生成,因為首先要有調試信息然后才能生成DSYM文件,而設置為NO,意味著不產生調試信息,所以也就沒辦法生成DSYM文件。所以建議不要設置。
-
Build Settings -> Deployment Postprocessing,Debug模式下設置NO,Release下設為YES
Deployment Postprocessing是Strip配置的總開關
圖片官方解釋:
?
Deployment Postprocessing (DEPLOYMENT_POSTPROCESSING), If enabled, indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values.
注意:Deployment Postprocessing是Strip配置的總開關,只有這個設置為YES之后,下面的Strip Linked Product、Strip Debug Symbols During Copy的設置才會生效。
-
Build Settings -> Strip Linked Product,Debug下設置為NO,Release下設置為YES
對最后生成的二進制文件進行strip,去除不必要的符號信息,Release下可以為YES。
官方解釋:
?
Strip Linked Product (STRIP_INSTALLED_PRODUCT), If enabled, the linked product of the build will be stripped of symbols when performing deployment postprocessing.
注意:如果Deployment Postprocessing不打開,該選項沒有作用。去除了符號信息之后需要使用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去除。
官方解釋:
?
Strip Debug Symbols During Copy (COPY_PHASE_STRIP), Specifies whether binary files that are copied during the build, such as in a Copy Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. It does not cause the linked product of a target to be stripped—use Strip Linked Product (STRIP_INSTALLED_PRODUCT) for that.
注意:如果Deployment Postprocessing不打開,該選項沒有作用
-
Build Settings -> Symbols Hidden by Default,Debug模式下設置為NO,Release下設置為YES
圖片Symbols Hidden by Default會把所有符號都定義成"private extern",移除符號信息
官方解釋:
?
Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN), When enabled, all symbols are declared private extern unless explicitly marked to be exported using attribute((visibility("default"))) in code. If not enabled, all symbols are exported unless explicitly marked as private extern. See Controlling Symbol Visibility in C++ Runtime Environment Programming Guide.
-
Build Settings -> Make Strings Read-Only設置為YES
圖片復用字符串字面量
官方解釋:
?
Make Strings Read-Only (GCC_REUSE_STRINGS), Reuse string literals.
-
Build Settings -> Dead Code Stripping設置為YES
圖片消除無效代碼,C/C++/Swift 等靜態語言編譯器會在 link 的時候移除未使用的代碼,對于OC等動態語言是無效的
官方解釋:
?
Dead Code Stripping (DEAD_CODE_STRIPPING), Activating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to turn on dead code stripping.
Pod優化,如果項目是OC的,但CocoaPod中有使用了Swift庫,打開了use_frameworks!,可以優化為針對單個Swift庫使用use_frameworks!而不是全部第三方庫都使用。代碼如下:
# Podfile
# 下面的這行代碼還是打開,不需要注釋掉
use_frameworks!
# 之前的其它代碼
# 然后添加下面的代碼,xxx是要打包成framework的庫
dynamic_frameworks = ['xxx']
pre_install do |installer|
installer.pod_targets.each do |pod|
if !dynamic_frameworks.include?(pod.name)
def pod.static_framework?;
true
end
def pod.build_type;
Pod::BuildType.static_library
end
end
end
end
在OC的項目中,Podfile如果引用了Swift的第三方庫,一般都會直接打開use_frameworks!,對應的Pod中所有的庫都會打包成動態庫,以及Swift和OC庫的依賴問題會導致依賴庫增加,會造成包體積增大。
修改方法有兩種:
這兩種修改方法都可以顯著減小包的體積。通過替代庫可避免導入Swift相關的依賴基礎庫,且對應的use_frameworks!可以注釋掉,相對來說,包會更小,但是改動比較大,需要把之前的庫替換掉。而通過hook的方式,針對個別的庫打包成動態庫,其余的打包成靜態庫,項目修改比較小,包體積也能減小。具體采用哪種視具體項目情況而定。
注意:Podfile按照上面的修改了之后,需要重新使用Pod install命令更新,然后編譯時可能會有報錯。
- 去除依賴的Swift第三方庫,找對應的OC庫替代;
- 通過Podfile里hook的方式,改動態庫為靜態庫
如果報的錯誤是,'RuntimeError - [Xcodeproj] Consistency issue: build setting
ARCHS
has multiple values:{"Debug"=>"$(ARCHS_STANDARD)", "Release"=>["arm64"]}
',可以先把Architecture中的值改為一樣的,Pod install之后再改為不同指令集的。-
因為之前第三方庫打包成framework的問題,如果遇到xxx framework not found的錯誤,在Build Setting中Other Linker Flags中進行修改,把對應的已經不是framework的庫從其中移除即可。示例如下:圖片
Asset Catalog Compiler編譯設置優化,Build Settings -> Asset Catalog Compiler - Options 中Optimization改為space
這個選項可以改變actool在構建Assets.car時選取的編碼壓縮算法,減少包大小。可以使用下面的命令檢查Assets.car中圖片的編碼壓縮算法。
// 可以把對應信息生成.json文件,用于對比不同
xcrun --sdk iphoneos assetutil --info Assets.car > Assets.json
比如筆者的設置了之后,對比查看信息如下,可發現壓縮的算法不同,占用空間的大小也不同- Build Settins -> Optimization Level改為-Oz
Optimization Level默認為-Os,-Oz是Xcode 11之后才出現的編譯優化選項,核心原理是對重復的連續機器指令外聯成函數進行復用,因此開啟Oz,能減少二進制的大小,但同時會帶來執行效率但額外消耗。可參考What's New in Clang and LLVM
在What's New in Clang and LLVM的Presentation Slides中,蘋果給出了Optimization Level各參數優化的選擇對比,如下圖,對于性能要求高的,建議選擇-O2和-O3,對于包大小敏感的,可選擇-Os和-Oz,默認-Os是性能和大小平衡比較好的。最終選擇什么,需要讀者根據自己實際項目而定。Optimization Level各參數對比:
官方解釋:
?
Smallest, Aggressive Size Optimizations: This setting enables additional size savings by isolating repetitive code patterns into a compiler generated function. -Oz
Xcode編譯設置優化總結如下:
優化結果
筆者最初項目大小最初為22.9M,設置了Pod優化之后大小縮減為21M,設置了Asset Catalog Compiler - Options 中Optimization為space之后,項目大小變為20.7M,再設置了上面其它Xcode編譯優化之后,項目變為13.2M(筆者把Architecture設為arm64)
設置Pod優化之后各部分對比
內容 | 大小 |
---|---|
_CodeSignature | 67 KB |
.lproj | 4 KB |
Frameworks | 21.4 MB |
Plugins | 181 KB |
Assets.car | 4.9M |
embedded.mobileprovision | 8KB |
Info.plist | 6 KB |
exec格式的xxx | 19.2 MB |
其它資源文件 | 1.4 MB |
簡單對比大小之后可以發現,Frameworks從35M減少到了21.4M,而exec文件從13M升到了19.2M,但總的ipa包大小變為了21M,比之前但22.9M降低了。
為什么會這樣呢?還記得Frameworks文件夾里放的是什么內容嗎?Framework中放的Pod中設置的第三方的動態庫、以及Swift Support庫。仔細觀察Frameworks文件夾中的內容,可以發現,之前在這里面的第三方的.framework,除了指定的打包成動態庫的第三方xxx、和Swift Support庫還在,其他的都不見了。
還記得筆者改的是什么嗎?筆者把Pod中第三方庫從都使用動態庫改為了個別使用動態庫、其它使用靜態庫。因為動態庫和靜態庫鏈接的方式的不同,動態庫鏈接時不復制,在程序啟動后用動態加載,所以是單獨放在Framework文件夾下;而靜態庫是鏈接時會被完整的復制到可執行文件中。所以,于是就出現了這樣的結果,Frameworks文件大小減少,而可執行文件大小增加。
設置了Asset Catalog Compiler - Options 中Optimization為space后對比
內容 | 大小 |
---|---|
_CodeSignature | 67 KB |
.lproj | 4 KB |
Frameworks | 21.4 MB |
Plugins | 181 KB |
Assets.car | 4.7M |
embedded.mobileprovision | 8KB |
Info.plist | 6 KB |
exec格式的xxx | 19.2 MB |
其它資源文件 | 1.4 MB |
對比之后發現,Assets.car中大小從4.9M減少到了4.7M,總的打包出來的ipa大小減少了0.3M,說明asset壓縮有效。
設置了Optimization Level為-Oz后對比
內容 | 大小 |
---|---|
_CodeSignature | 67 KB |
.lproj | 4 KB |
Frameworks | 21.4 MB |
Plugins | 181 KB |
Assets.car | 4.7M |
embedded.mobileprovision | 8KB |
Info.plist | 6 KB |
exec格式的xxx | 18.9 MB |
其它資源文件 | 1.4 MB |
對比之后發現,exec的可執行文件大小從19.2M減少到了18.9M,說明優化有效,但總的打包出來的ipa大小并沒有改變(上傳到fir后,包小了0.03M),故而最終這個選項筆者最終沒有打開。
Xcode其它編譯設置優化之后對比
內容 | 大小 |
---|---|
_CodeSignature | 67 KB |
.lproj | 4 KB |
Frameworks | 11.1 MB |
Plugins | 83 KB |
Assets.car | 4.7M |
embedded.mobileprovision | 8KB |
Info.plist | 6 KB |
exec格式的xxx | 10.3 MB |
其它資源文件 | 1.4 MB |
簡單對比大小之后可以發現,Frameworks從21.4M減少到了11.1M,Plugins從181KB減少到了83KB,exec文件也從19.2M減少到了10.3M。
同樣為什么會這樣呢?筆者設置了strip移除了符號信息,設置了打包只生成arm64架構指令集的包,所以Framework和exec都變小了,那Plugin呢?Plugin中是App創建的擴展,筆者針對通知擴展,也設置了只生成arm64指令集的包,所以Plugin也變小了。
優化總結
也許有人覺得筆者目前項目包沒有太大,做這個優化是否有意義?在筆者看來,包大小優化應該是習慣,不是因為包大了才去優化,而是因為覺得有優化空間,所以才優化。打包出來ipa的包小,說明歷史負擔不重,船小好調頭,編譯打包速度也快,而且試錯成本也低,恰恰是正應該優化的時候。優化總結出來的經驗教訓,落入文檔,后續開發時能時刻注意,對開發來說是更好的。
資源文件優化
資源文件的優化,通常來說是比較簡單的,但是資源文件的優化是需要持續進行的,前面介紹的Xcode編譯設置優化,配置好了之后,后續開發過程中只要不修改配置,都無需重復關注。但資源文件不同,隨著項目的迭代,會不斷引入新的資源文件,不斷有廢棄資源的產生,所以資源文件的優化是要持續進行的。
資源文件的優化分為兩部分,即:無用資源的刪除、已用資源的壓縮。在這里建議分先后順序,即先做刪除再做壓縮,因為如果先壓縮了,結果發現是無用資源,就白白浪費了力氣。
無用資源的刪除:
- 已定義未使用的代碼文件
- 已廢棄業務,代碼還在
- 已引用的圖片但未使用
- 某些重復資源導入
已用資源的壓縮:
- 項目中引入圖片、網頁、json、音頻等文件的壓縮
下面一步步的來實踐:
無用資源的刪除
隨著項目的迭代,每個項目都會或多或少存在冗余。可能是開發了的功能未上線但產品讓保留,保留著保留著就忘記了;可能是已下線的業務,沒人通知到開發,于是代碼邏輯一直都在;可能是刪除某些業務代碼時,對應的圖片資源未刪除;又或者是每個開發,導入各自熟悉的第三方庫使用。
這部分的優化在筆者看來,也分為兩步,一是預防,一是治理。筆者個人感覺預防比治理更重要。因為預防是前置,預防到位了,治理就會很輕松。
預防
上面說的場景,讀者可能都遇到過,那要怎么做才能避免出現上面的情況,或者盡量少出現上面的情況?
筆者個人想法是通過規范的流程來,有一套規范的流程,按流程執行,避免出現信息不對等的情況。
產品和開發之間的信息不對等,會導致業務相關的冗余,產品知道具體業務的數據,而開發不清楚,所以可以通過定期同步給開發,讓開發也能了解到對應業務是否活躍,從而及時對項目進行優化。
開發之間的信息不對等,會導致各自開發自己的,重復造輪子,所以可以通過建立公共文檔,開發的流程規范、項目使用第三方庫的規范、設計規范、代碼規范都列舉出來,每個人都能根據對應的文檔了解到對應項目的信息,每個人開發都應該有一套統一的標準。這樣就可以避免一人一套代碼的問題。
具體的規范流程讀者可以針對自己公司的實際情況來,可以思考一下,之前開發中是否出現了類似的問題,出現了之后是否有改變,怎么能避免再次出現同樣的問題?
治理
針對無用資源的刪除
-
已定義未使用的代碼
可使用AppCode進行分析,打開AppCode,待索引完成后,選擇頂部菜單中的Code->Inspect Code,然后選擇范圍,whole Project點擊OK,等待AppCode靜態分析即可。靜態分析完以后,可以在Unused code里看到所有的無用代碼。圖片圖片AppCode中無用代碼靜態分析的類型有以下幾種:
-
內容 大小 Unused class 無用類 Unused import statement 無用類引入聲明 Unused property 無用屬性 Unused method 無用方法 Unused parameter 無用參數 Unused instance variable 無用實例變量 Unused local variable 無用局部變量 Unused value 無用值聲明 Unused macro 無用宏定義 Unused global declaration 無用全局證明 AppCode靜態分析結果出來之后,刪除前要經過確認,因為靜態分析的結果可能會有誤差,比如針對performSelector調用的方法就會被檢測為沒有調用。
-
已廢棄業務,代碼還在
需要梳理業務流程,結合線上業務數據點擊量,同產品和業務確認對應功能是否下線,從而決定是否移除對應的業務模塊代碼。
-
已引入未使用圖片
推薦使用工具LSUnusedResources,原理大致是遍歷資源目錄下后綴 ["imageset", "jpg", "png"...] 的文件,然后在源文件 ["m", "swift", "xib", "storyboard"...] 中字符串匹配,無匹配則是無用的資源文件。
使用時注意勾選Ignore similar name,然后點擊右上角的Browse選中要掃描的項目地址,點擊右下角的search,就會開始掃描,結果會在底部Unused Results中展示出來,然后CMD+A全選,export,導出到一個文本文件中。也可以在對應單條Item上面雙擊,會打開對應的文件夾。建議刪除前在項目中搜索確認,是否確實沒有使用(類似字符串中間替換的可能會被掃描出來,所以刪除前需要確認)
圖片 -
某些重復資源的導入
重復資源的導入,分為兩個方面,一方面是針對第三方SDK,另一方面是項目文件。
-
針對第三方SDK
項目中功能類似的SDK建議保留一個,比如埋點統計的友盟、TalkingData等,線上日志分析的聽云、Bugly等,又或者網絡請求、UI布局的類庫,建議分析相同功能的類庫,結合實際情況,保留一個即可;另外,有些第三方類庫導入時,可只導入實際使用的部分,不需全量導入,也是可以優化的地方。
-
針對項目文件
使用 fdupes 工具進行重復文件掃描,原理是:通過校驗所有資源的 MD5,篩選出項目中的重復資源,文件比較順序是大小對比 > 部分 MD5 簽名對比 > 完整 MD5 簽名對比 > 逐字節對比。來自:包體積大小:瘦身
fdupes使用如下:
// 1. 首先安裝fdupes
brew install fdupes
// 2. 使用,其中xxx是要掃描的目錄,yyy.txt是掃描結果輸出的文件
fdupes -Sr /Users/.../xxx/ > /Users/.../yyy.txt
輸出結果類似于下面這樣,通常相同文件可通過修改引用,僅保留一份源文件就可以。
已用資源的壓縮
-
項目中引入圖片、網頁、json、音頻等文件的壓縮
網頁的壓縮指的是,放入APP資源中的js文件,最好是經過H5端壓縮后的。
json文件的壓縮,如果不是打開APP時馬上要用到的數據,可采取把對應資源放到服務端,下載后使用。
音頻文件的壓縮,則是在可接受的范圍之內,選擇系統可支持的壓縮比率高的格式。
而最需要注意的是圖片的壓縮,圖片的壓縮,分為幾個部分
Compress PNG Files
打包的時候自動對圖片進行無損壓縮Remove Text Medadata From PNG Files
移除 PNG 資源的文本字符resource_bundles:允許定義當前 Pod 庫的資源包的名稱和文件。用 hash 的形式來聲明,key 是 bundle 的名稱,value 是需要包括的文件的通配 patterns。CocoaPods 官方強烈推薦使用 resource_bundles,因為用 key-value 可以避免相同名稱資源的名稱沖突。同時建議 bundle 的名稱至少應該包括 Pod 庫的名稱,可以盡量減少同名沖突。使用resource_bundles會為指定的資源打一個.bundle,所以獲取圖片時要注意指定bundle的位置:
NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/PAX.bundle"];
NSBundle *resource_bundle = [NSBundle bundleWithPath:bundlePath];
UIImage *image = [UIImage imageNamed:@"xxxx" inBundle:resource_bundle compatibleWithTraitCollection:nil];
- resources:使用 resources 來指定資源,被指定的資源只會簡單的被 copy 到目標工程中(主工程)。官方認為用 resources 是無法避免同名資源文件的沖突的,同時,Xcode 也不會對這些資源做優化。
-
單色圖標、簡單的功能圖標,建議使用IconFont,矢量圖標庫的格式,既能統一規范格式,又能減少資源文件大小。圓角和陰影圖片盡量代碼實現。
IconFont的使用可參考筆者之前寫的Demo——IconFont
-
針對普通圖片,可以調用tinyPNG API進行壓縮,這里可以使用筆者之前修改的腳本BatchProcessImage,調用的是tinyPNG的API可以一次性壓縮500張圖片,而且只需要指定項目目錄,會自動壓縮后替換原來的圖片,不需要手動導入導出圖片。使用可參考鏈接BatchProcessImage,需要注意的是注意python版本,python3和python,以及pip3和pip的選擇,安裝依賴庫的時候使用的哪個python版本,最后調用的腳本命令的時候就要用對應的python版本。
另:如果項目圖片超出500張,可以修改一下腳本文件,即:壓縮過程中把壓縮處理過的圖片存儲下來,然后第二次執行時,對于壓縮過的不處理,就可以接著上次壓縮到的繼續壓縮了。而如果需要對壓縮過的再次壓縮,只需要把存儲下來的壓縮過的圖片名字清除即可。
放入xcassets里的2x和3x圖片,在上傳時,會根據具體設備分開對應分辨率的圖片,不會同時包含。而放入Bundle中的都會包含。所以要盡量把圖片放入xcassets中。但是,根據抖音品質建設 - iOS 安裝包大小優化實踐篇中介紹的,Assets.car編譯過程中有時會選擇一些圖片,拼湊成一張大圖來提高圖片的加載效率。被放進這張大圖的小圖會變為通過偏移量的引用,建議使用頻率高且小的圖片放到Asset.car中,Asset.car能保證加載和渲染速度最優。但是大的圖片(大于100K)就不要放入Asset.car中了。大的圖片可以考慮將圖片轉成WebP。WebP是Google公司的一個開源項目,能夠把圖片壓縮到很小,但是肉眼看不出來差別,目前iOS常用的圖片顯示類庫都用支持該格式解析的拓展。可使用iSparta進行批量轉換。
私有Pod庫中的資源文件,建議在Pod庫里面的Resource目錄下新建Asset Catalog文件,命名為Images.xcassets,私有庫使用的圖片放入這里,然后手動修改該SDK的podspec,指定resource_bundles使用Images.xcassets。
s.resource_bundles = {
'xxsdk' => ['xxx/Assets/.../*.xcassets']
}
Ps: Pod資源文件引用方式有 resource_bundles 和 resources,推薦使用 resource_bundles。參考深入探索 iOS 包體積優化
- 最后,是Xcode中關于圖片壓縮的設置,有時候壓縮了圖片之后,發現包大小并沒有改變太多,可能是因為Xcode的Compress PNG Files選項的原因。建議如果自己對圖片進行了壓縮,就可以把Xcode的Compress PNG Files設置為NO。
資源文件優化總結如下:
優化結果
筆者項目的ipa大小從Xcode編譯優化后的大小為13.2M,經過了資源文件優化之后大小縮減為10.3M。
資源文件優化之后各部分對比
內容 | 大小 |
---|---|
_CodeSignature | 67 KB |
.lproj | 4 KB |
Frameworks | 11.1 MB |
Plugins | 83 KB |
Assets.car | 2.4M |
embedded.mobileprovision | 8KB |
Info.plist | 6 KB |
exec格式的xxx | 10.3 MB |
其它資源文件 | 952 KB |
簡單對比之后發現,Assets.car從4.7M減少到2.4M,其它資源文件從1.4M減少到952KB,說明資源壓縮有效。
優化總結
見圖,??
監控機制?
瘦身完成之后,如何保證包大小不會再次迅速增大?就像減肥之后不會迅速反彈一樣?就需要依賴適當的監控機制和合理的流程規范來控制。
監控機制保證實時發現問題,每次打包完成后,運行腳本比較包大小差異,如果有增大超過了設置的閾值,則郵件通知相關開發,開發關注排查是什么原因導致包大小變大;同時做好記錄,每次打包的包大小變化及時注意記錄,造成包大小變化的原因,落入文檔。
流程規范是用于保證每個項目開發者知曉開發中注意什么,養成好的開發習慣,避免造成包大小的突然變大。
- 引入新的三方庫時,要考慮是否已有同類型的庫,是否可以自己實現,是否會造成體積增大。盡量避免Objective-C和Swift混編,優先引用相同語言類型的庫
- 新增的圖片資源,關注大小,考慮是否能用Iconfont,是否能代碼實現,注意放入項目的位置,如果體積太大,壓縮后使用
- 廢棄模塊不要保留,及時清理
- 及時關注包大小變化,對于包大小變化的原因及結果整理落入文檔,反饋總結
總結
截止目前為止,筆者總體項目優化結果如下:
優化內容 | ipa大小 |
---|---|
原始 | 22.9M |
Xcode編譯優化-Pod優化后 | 21M |
Xcode編譯優化-Asset Catalog Compiler編譯設置優化后 | 20.7M |
Xcode編譯優化-其它 | 13.2M |
資源優化 | 10.3M |
達到了預期瘦身的效果,雖然還有更進一步優化的空間,比如把項目中唯一引用的Swift的第三方庫改為OC的,從而可以去除混編,能大幅縮減項目大小,但是由于需要改動業務代碼,筆者暫時擱置了。
總的來說,筆者在業務代碼沒有改動的情況下,經過Xcode編譯優化和資源文件壓縮,把包大小從22.9M壓縮到了10.3M,就結果來說是超出了預期。
但是作為優化包大小的實踐來說還有待未完待續的地方,就是最后一步的代碼優化,筆者打算單獨抽一篇文章,來補充一下,使用代碼優化的流程邏輯。
引用
- 包體積大小:瘦身2. iOS包體積優化3. 抖音品質建設 - iOS 安裝包大小優化實踐篇4. 干貨!京東商城iOS App瘦身實踐5. iOS 優化IPA包體積(今日頭條)6. 深入探索 iOS 包體積優化7. 干貨|今日頭條iOS端安裝包大小優化—思路與實踐8. 今日頭條 iOS 安裝包大小優化—— 新階段、新實踐9. iOS 優化ipa包,減小安裝包大小10. iOS微信安裝包瘦身