iOS 优化 - 瘦身
前言
iOS 優化將是一個專題,其中會包括包體積優化(瘦身)、啟動時間優化、UI 優化等等。那么這個專題的開篇就從瘦身開始吧。
APP 的大小是分為 APP 下載大小和安裝大小兩個概念的。
-
下載大小是指 App 壓縮包(也就是 .ipa 文件)所占的空間,用戶在下載 App 時,下載的是壓縮包,這樣做可以節省流量;
-
當壓縮包下載完成后,就會自動解壓,解壓過程也就是通常所說的安裝過程;安裝大小就是指壓縮包解壓后所占用的空間。
用戶在商店看到的大小是安裝大小。如果想看安裝包在各機型上的下載、安裝大小可以在 App Store Connect 后臺查看。
下載及安裝大小示意圖
App Store OTA 下載大小限制:
雖然蘋果歷年都會調整 App 下載大小,由之前的 100M 到后來的 150M 再到現在的 200M。如今,App 下載大小超出 200 MB 時 ,會出現兩種情況:
-
iOS 13 以下的用戶,無法通過蜂窩數據下載 App;
-
iOS 13 及以上的用戶,需要手動設置才可以使用蜂窩網絡下載 App。
Apple __TEXT 段大小限制:
-
iOS 7 之前,二進制文件中所有的 __TEXT 段總和不得超過 80 MB;
-
iOS 7.X 至 iOS 8.X,二進制文件中,每個特定架構中的 __TEXT 段不得超過 60 MB;
-
iOS 9.0 之后,二進制文件中所有的 __TEXT 段總和不得超過 500 MB。
順便給大家說下蘋果將下載大小限制由 100M 調整到 150M 的原因是什么?主要原因就是 Uber 當年用 Swift 重構開發 APP 時,隨著業務的增長,后期發現實在無法再將 APP 尺寸降到 100M 以下,只能聯系蘋果讓其將下載大小提升到 150M,同時蘋果的 Swift 團隊還幫助添加了一些編譯器選項 (-Osize)。
該文主要研究的是如何降低 APP 的下載大小的,因為文章篇幅較長,如果大家不想細讀,可以直接跳過細節展開看每個小節的結論部分。
瘦身方向
將 ipa 安裝包后綴名改為 zip,將其解壓,顯示.app 包內容后,就可以很直觀的看到安裝包的組成部分。一般會包括以下幾個部分:
-
Exectutable: Mach-O 可執行文件
- Resources:資源文件
-
圖片資源:Assets.car/bundle/png/jpg 等
-
視頻 / 音頻資源:mp4/mp3 等
-
靜態網頁資源:html/css/js 等
-
視圖資源:xib/storyboard 等
-
國際化資源:xxx.lproj
-
其他:文本 / 字體 / 證書 等
-
- Framework:項目中使用的動態庫
-
SwiftSupport: libSwiftxxx 等一系列 Swift 庫
-
其他依賴庫:Embeded Framework
-
- Pulgins:Application Extensions
-
appex:其組成大致與 ipa 包組成一致
-
其實核心組成部分便是資源文件與Mach-O 可執行文件兩部分,這兩個部分便是我們的主要瘦身方向。在瘦身過程中,應該盡量使用 ROI 最高的優化手段,付出更少的精力,得到更多的收益。
在介紹我們作為開發者的優化方向之前,我們先看一下蘋果自身對于 APP 下載大小的優化有哪些吧,我們要充分利用 Apple 自身的優化機制。
App Thinning(蘋果自身優化)
App Thinning 是指 iOS9 以后引入的一項優化,Apple 會盡可能,自動降低分發到具體用戶時所需要下載的 App 大小。其主要包含以下三項功能。
Slicing(應用分割)
當向 App Store Connect 上傳 .ipa 后,App Store Connect 構建過程中,會自動分割該 App,會專門針對不同的設備來選擇只適用于當前設備的內容(主要是架構和資源)以供設備下載。其差異性主要是體現在架構(32 位還是 64 位)和資源(@1x、@2x 還是 @3x)等方面上。
其中架構方面開發者不需要去控制,但是對于資源來說要求圖片在 Asset Catalog 管理,如果直接放在 Bundle 中,則不會被優化。
關于 Asset Catalog 相關知識點及優化結論可見下文 Assets Catalog 章節。
總結:盡量將圖片等資源交給Asset Catalog管理。
Bitcode(中間碼)
Bitcode 是一個編譯好的程序的中間表示形式(IR)。上傳到 App Store Connect 中的包含 Bitcode 的 App 將會在 App store 中進行鏈接和編譯。蘋果會對包含 Bitcode 的二進制 app 進行二次優化,而不需要提交一個新的 app 版本到 app store 中。屬于 Apple 內部的優化,但需要注意;
-
全部都要支持。我們所依賴的靜態庫、動態庫、Cocoapods 管理的第三方庫,都需要開啟 Bitcode。否則打包會編譯失敗,具體錯誤會在 Xcode 中指出;
-
Crash 定位。開啟 Bitcode 后最終生成的可執行文件是 Apple 自動生成的,同時會產生新的符號表文件,所以我們無法使用自己包生成的 DYSM 符號化文件來進行符號化,而是使用使用 Apple 生成的 DYSM 符號化文件;
-
Flutter 不支持 Bitcode,如果項目是包含 Flutter 框架的,就無法使用這種方式;
-
BitCode 在 iOS 開發中是可選的,在 watchOS 開發中是必須要選擇的, Mac OS 是不支持 BitCode 的。
開啟方式: Build Settings -> Enable Bitcode -> 設置為 YES 。
如果想對 Bitcode 了解更深入一些,可以看下我之前的一篇博文--iOS 編譯簡析。
結論:可根據項目實際情況決定是否開啟,如果項目混編了 Flutter、依賴的部分庫不支持 Bitcode 以及不想處理一遍 DYSM 符號化,就不要進行開啟,否則可以選擇開啟。
On-Demand Resources(隨需應變資源)
On-Demand Resource 即一部分圖片可以被放置在蘋果的服務器上,不隨著 App 的下載而下載,直到用戶真正進入到某個頁面時才下載這些資源文件。
應用場景:相機應用的貼紙或者濾鏡、關卡游戲等。
開啟方式: Build Settings -> Enable On Demand Resources -> 設置為 YES(默認開啟)。
設置資源的 Tag 類型,種類包括:
-
Initial install tags:資源和 App 同時下載。在 App Store 中,App 的大小計算已經包含了這部分資源。當沒有 NSBundleResourceRequest 對象訪問它們時,它們將會從設備上清除。
-
Prefetch tag order: 在 App 安裝后開始下載,按照預加載列表中的順序依次下載。
-
Dowloaded only on demand: 只有在 App 中發出請求時才會下載。
如果項目中有 Demand Resources,則最后生成的安裝包結構大致層級為:
-
項目名.app
-
OnDemandResources 文件夾
具體使用方法這里就不展開講了。
我們在下載安裝包時,不會下載 OnDemandResources 文件夾中的資源,起到減小下載安裝包尺寸的目的。
結論:該方式與下文提到的資源遠程化本質一樣,只不過一個是放在自己服務器,一個是放在蘋果服務器,可根據自己項目實際情況選擇是否使用。
資源文件瘦身
資源文件優化方向比較多,相對優化 Mach-O 可執行文件來講,風險也比較小。
去除無用 / 重復的資源
業務的迭代開發,出現無用的圖片資源是比較正常的,我們可以借助工具找出哪些圖片資源沒有被使用過。推薦下面兩款工具:
-
LSUnusedResources:可視化客戶端工具;
-
FengNiao:命令行工具,可嵌入到 Run Script 中或者在 CI 系統中使用,支持的模式匹配更加強大。
因為這類工具的原理都是在相關文件(.m、.swift 等等)中利用正則表達式檢測是否有圖片名稱的字符,所以存在以下問題。 問題點:
-
如果代碼中使用的圖標名稱是拼接而成的,就會誤以為相關圖片是廢棄圖片;
-
如果 Assets.xcassets 文件中直接修改了圖片的名字,也會認為相關圖片可能是廢棄圖片;
可以利用Duplicate Photos從內容上檢測重復/相似圖片。
引申一下:
之所以要使用自動化工具來檢測重復資源的原因是因為資源是 弱類型,我們在項目迭代過程 中手動去維護是相當麻煩的一個過程。轉換一下思維,如果資源變成強類型了, 那我們維護起來就相當容易了。目前就有這樣一個工具 R.swift一定意義上將資源變成強類型, 類似于 Android 開發中的 R 文件。
可利用fdupes查找項目中的重復文件。其原理是對比不同文件的簽名,簽名相同的文件就會判定為重復資源。
mac 上可直接通過 brew install fdupes 進行安裝,可以使用 fdupes -Sr 文件夾名稱 來查看所有涉及到的目錄和子目錄中的重復文件的大小,其余相關指令可自行查閱,不建議使用 fdupes 相關命令直接刪除搜索出來的重復資源,風險比較高。
結論:考慮到工具的不準確性,可以利用工具粗檢測一下哪些資源沒有被使用,然后經人工確認后才統一進行刪除。對于工具無法檢測出來的資源,就只能人工進行篩查了,可每人分配幾個模塊,提高效率。
資源壓縮
請注意:這里的資源不包括 Assets Catalog 管理的資源。
PNG 資源
這一部分涉及前因后果比較多,為保證大家能看懂,會先鋪墊一些原理性知識,請耐心閱讀。
Xcode 的 Build Setting 提供的給我們兩個編譯選項來幫助壓縮 PNG 資源 。
Remove Text Medadata From PNG Files(默認開啟):能幫助我們移除 PNG 資源的文本字符,比如圖像名稱、作者、版權、創作時間、注釋等信息。 Compress PNG Files(默認開啟):當設置為 YES 后,打包的時候會利用 pngcrush 工具自動對項目中所有 PNG 圖片進行無損壓縮以及修改文件格式,該工具是開源的--pngcrush 地址。
Compress PNG Files 設置為 YES 后,XCode 會調用該路徑的腳本 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/iphoneos-optimize。
pngcrush 工具在其同級目錄存放,iphoneos-optimize 腳本中關于 PNG 壓縮的內容如下:
sub?optimizePNGs?{my?$name?=?$File::Find::name;if?(?-f?$name?&&?$name?=~?/^(.*)\.png$/i)?{my?$crushedname?=?"$1-pngcrush.png";//?$PNGCRUSH便是pngcrush工具路徑my?@args?=?(?$PNGCRUSH,?"-q",?"-iphone",?"-f",?"0"?);if?(?$stripPNGText?)?{push?(?@args,?"-rem",?"text"?);}push?(?@args,?$name,?$crushedname?);if?(system(@args)?!=?0)?{print?STDERR?"$SCRIPT_NAME:?Unable?to?convert?$name?to?an?optimized?png!\n";return;}unlink?$name?or?die?"Unable?to?delete?original?file:?$name";rename($crushedname,?$name)?or?die?"Unable?to?rename?$crushedname?to?$name";print?"$SCRIPT_NAME:?Optimized?PNG:?$name\n";} }從內容上來看,腳本是通過png 后綴名來判斷是否為 png 圖片,如果圖片改變后綴名,則該圖片則不會被 pngcrush 工具進行處理。
我們可以通過下面命令手動使用 pngcrush 工具。
#?image.png?經編碼后?生成?image1.png xcrun?-sdk?iphoneos?pngcrush?-iphone?-f?0?image.png?image1.png#?image.png?經解碼后?還原成?image1.png #?即使還原回去與原圖片也不會完全一致,這里就不展開描述了 xcrun?-sdk?iphoneos?pngcrush?-revert-iphone-optimizations?image.png?image1.pngpngcrush 工具編碼結果主要變化內容如下:
-
在 IHDR 塊之前插入了 CgBI 塊來表示這種格式
-
修改 IDAT 塊中的數據,去除 zlib 壓縮頭和 Adler-32 校驗和;
-
八位真彩色圖像按 BGR/BGRA 順序存儲,而不是按 IHDR 塊中指示的 RGB 和 RGBA 順序存儲;
-
圖像像素使用預乘 alpha;
-
修改后的文件使用。png 為有效圖像定義的文件擴展名以及內部文件結構,但符合 PNG 的查看和編輯軟件不再能夠處理它們;
-
增加了一個 iDot 數據塊,是 Apple 自定義的數據塊,暫時不知其作用;
其本質是使正常的 png 圖片變成了一個優化后的 CgBI 格式的 png。可以利用pngcheck查看處理前的圖片信息,利用pngdefry查看處理后的圖片信息(其還可以將 CgBI 格式的 png 還原回去,這個功能跟 pngcrush 工具解碼功能類似)。
從上述變化內容來看, pngcrush 工具編碼過程并不是簡單的壓縮數據,更重要的是對文件格式做了修改。因為 iPhone 中,圖像是以 BGRA 格式在內存中處理的,所以修改后的格式變成了 iPhone 能更方便處理的格式,加快處理速度。
根據我自己測試的壓縮效果來看,對于 Bundle 中放置的 png 圖片,經過 pngcrush 的處理,大小不降反增,目前暫時沒有找到哪些具體因素影響其壓縮效果。
pngcrsuh測試效果對比圖
結論:Compress PNG Files 雖然是壓縮 PNG,但其最主要的目的并不是為了壓縮圖片大小, 而是將 PNG 轉換成 iOS 更容易處理、更塊速度的去識別的格式,可以根據項目在開啟、關閉兩種情況下的打包大小,自行取舍。
非 PNG 資源
非 PNG 資源壓縮包含兩種方式:
-
直接通過一些壓縮工具將資源進行壓縮,格式保持不變,如一些圖片資源、音視頻資源等,圖片壓縮工具下文會有介紹。
- 還有一些文本資源,如 json 文件、html 文件等,無法使用上述的方式壓縮,可以采用壓縮成 zip 等壓縮格式的方式,可分為三步:
-
壓縮階段:在 Build Phase 中添加腳本,構建期間對白名單內的文本文件做 zip 壓縮;
-
解壓階段:在 App 啟動階段,在異步線程中進行解壓操作,將解壓產物存放到沙盒中;
-
讀取階段:在 App 運行時,hook 讀取這些文件的方法,將讀取路徑從 Bundle 改為沙盒中的對應路徑;
結論:可選用合適的壓縮工具對音視頻、非 Assets Catalog 管理的圖片資源進行壓縮。對于一些比較大的文本文件可選用第二種運行時解壓讀取的方式,如 Lottie 動畫的 json 文件。
Assets Catalog
Assets Catalog 涉及的技術點比較多,后續可能會單獨開一篇博文專門講這一部分內容。
去除 @1x 圖片
@1x 圖是 iPhone 3Gs 用的,iPhone 4 開始使用 @2x 圖了,iPhone 6p 開始使用 3x 圖。
結論:可以刪除 Assets 中所有 @1x 的圖片資源。
圖片壓縮
Assets.car 文件是工程中 Asset Catalog 的構建產物。Xcode 構建過程中,在 compile asset catalog 節點時, 構建 Asset Catalog 的工具 actool 會首先對 Asset Catalog 中的 png 圖片進行解碼,得到 Bitmap 數據,然后再運用 actool 的編碼壓縮算法進行編碼壓縮處理。
如果你開發時放入到 Assets 中的是 jpg 格式文件,在最終生成的 Assets.car 文件中也會成為 png 圖片。
xcrun assetutil --info Assets.car ??墒褂迷撁顧z查 Assets.car 中每張圖片使用的編碼壓縮算法。
目前 actool 會使用的壓縮算法包括 lzfse 、 palette_img 、 deepmap2 、 deepmap_lzfse 、 zip ,影響其使用何種算法的因素包括 iOS 系統版本、ASSETCATALOG_COMPILER_OPTIMIZATION 設置(位于 Build Setting 中)等;
-
iOS 11.x 版本:對應的壓縮算法為 lzfse、zip;
-
iOS 12.0.x - iOS 12.4.x: 對應的壓縮算法為 deepmap_lzfse、palette_img;
-
iOS 13.x: 對應的壓縮算法為 deepmap2 ;
按照壓縮比來講 lzfse < palette_img ~= deepmap_lzfse < deepmap2
如果設置了 ASSETCATALOG_COMPILER_OPTIMIZATION 為 space 那么在低版本 iOS 系統上,使用 lzfse 壓縮算法的圖片會變成 zip 的算法,可減少 iOS 11.x 及以下的 iOS 設備圖片的占用大小。其他 iOS 版本的壓縮算法不受這個配置的影響。
-
無損壓縮通過變換圖片的編碼壓縮算法減少大小,但是不會改變 Bitmap 數據,對于 actool 來說,它接收的輸入(Bitmap 數據)沒有改變,所以無損壓縮無法優化 Assets.car 的大小,但是可以用來優化非 Asset Catalog 管理的圖片。
-
使用有損壓縮方式并采用合適的壓縮方法是可以減小 Assets.car 的大小??梢詫D片采用RGB with palette(調色板算法)編碼方式來達到圖標壓縮的效果,這種編碼方式進行壓縮特別適合內部顏色相對接近的圖標。但是需要注意如果圖片中有半透明效果,這種壓縮方式可能會導致半透明的地方出現噪點,所以壓縮之后請注意仔細檢查一下。
RGB with palette 編碼的得到的字節流首先維護了一個顏色數組。顏色數組每個成員用 RGBA 四個分量維護一個顏色。圖像中的每個像素點則存儲顏色數組的下標代表該點的顏色。顏色數組維護的顏色種類和數量由圖片決定,同時可以人為的限制顏色數組維護顏色的種類的上限,默認為最大值 256 種,具體原理詳見底部相關鏈接 --【Palette Images】;
使用下文提到的 ImageOptim-CLI 工具,我們可以改變圖片的編碼方式為 RGB with palette,命令如下:
imageoptim -Q --no-imageoptim --imagealpha --number-of-colors 16 --quality 40-80 ./1.png
--number-of-colors:控制顏色數組維護顏色的數量; --quality:控制圖片的質量變為原來的百分比; 命令中的數值可以在顯著減少包大小的同時維持肉眼看不到的質量變化。
以圖片資源舉例,我們可以使用工具對其進行壓縮,推薦幾款工具如下:
-
TinyPng:網頁工具,有損壓縮;
-
TinyPNG4Mac:TinyPng 的客戶端工具,無需聯網使用瀏覽器;
-
ImageOptim: 客戶端工具,支持無損壓縮及有損壓縮兩種形式,可自定義設置壓縮方式。
-
ImageOptim-CLI:Mac 可使用brew install imageoptim-cli安裝,其會根據你的指定,選擇性調用 JPEGmini、ImageAlpha、ImageOptim 等工具,實現中間過程自動化。
如果想將 car 文件中的 png 提取出來,可以使用Asset Catalog Tinkerer。
引申一下,好的工具是開發利器,之前整理了一些好用的 Mac 效率工具,可見Mac 效率軟件。
結論:能用 Asset Catalogs 管理的資源,盡量使用 Asset Catalogs 來管理。使用 Asset Catalog 管理圖片不要對圖片進行無損壓縮,最終起不到壓縮效果,如果想要壓縮,可以采用上面所提到的有損壓縮方式,并檢查壓縮后的效果。
圖片資源使用 Webp 格式
谷歌開源的格式,Webp 壓縮率比較高,同時支持有損和無損兩種壓縮模式,可以帶來更小的圖片體積,而且肉眼看不出差異。根據 Google 的測試,無損壓縮后的 WebP 比 PNG 文件少了 26%的體積,有損壓縮后的 WebP 圖片相比于等效質量指標的 JPEG 圖片減少了 25%~34% 的體積。
但是 WebP 與 JPG 以及 PNG 相對比,在編解碼的 CPU 消耗以及解碼時間上會差一些,因為編碼是用戶上傳圖片時的一次性操作,并且編碼過程是在服務器后臺進行,對用戶的影響不大,對用戶影響大的主要是解碼過程,會導致圖片加載速度慢一些。所以,我們需要根據項目的實際情況在性能和體積上做取舍。
如果從服務器帶寬以及流量來看,因為圖片的體積變小,所以會減小帶寬,降低成本。
推薦二種轉 WebP 格式的方法
-
iSpart:騰訊出品,GUI 工具;
-
webp 工具: 在 Mac 下,可以使用 Homebrew 安裝 WebP 工具--brew install webp;
iOS 原生并不支持 WebP 格式加載,需要引入 SDWebImage/WebP,或者進行自研。
結論:該方案適合整個大前端及后端統一調整,整體進行優化,如果是單一的客戶端進行調整,可能達不到最優效果。
資源動態化
除了上文提到的使用 On-Demand Resources 方式將部分資源放在蘋果服務器之外,我們也可以將一些本地資源轉移到自己的服務器上去。這樣不僅降低了安裝包大小,也將這些資源動態化了。適合放在服務器的資源應包含以下幾個特性:
-
不影響首屏加載體驗;
-
變化頻率較高;
-
尺寸很大;
如一些 Banner 廣告圖、主題資源、音視頻資源、H5 資源資源。
結論:可盡量將滿足上述特性的資源放置在服務器。
圖標優化
-
使用 tint color 精簡單色圖標;
-
使用圖標字體(IconFont)替換單色圖標;
-
將部分相似圖標進行整合;
結論:如果項目有相對的設計規范及標準圖標樣式,使用圖標字體是一個很好的方案。剩余的優化點根據項目實際情況決定是否使用。
編譯選項改進
Xcode 支持編譯器層面的一些優化選項,通過修改 Build Setting 的一些相關配置,可以讓我們介于更快的編譯速度和更小的二進制大小并且更快的執行速度之間自由選擇想要進行的優化粒度,這些選項有的會影響資源文件,有的會影響可執行文件,因為內容比較多,所以起一個獨立的章節描述。
這種方式的性價比很高,改動一項配置,就可能會帶來收益,但是可能具有一定的風險,需要謹慎。
下文中提到的一些 Xcode 默認配置可能在低版本 Xcode 上不是默認配置,如果不是默認,可手動勾選。
去除無用架構
可以在 Build Setting - Excluded Architectures 項設置排除的架構。
先看一下幾種架構的含義:
-
模擬器 32 位處理器測試需要 i386 架構;
-
模擬器 64 位處理器測試需要 x86_64 架構;
-
真機 32 位處理器需要 armv7, 或者 armv7s 架構;
-
真機 64 位處理器需要 arm64 架構。
| iPhone iPhone2 iPhone3G 第一代和第二代 iPod Touch | iPhone4 iPhone4S iPad1-iPad3,3、4 代 iPod Touch iPad mini | iPhone5 iPhone5C iPad4 | iPhone 5S 等剩余全部機型 |
結論:理論上只保留 arm64 架構其實就夠用了,可以去除 armv6 、 armv7 、 armv7s 三種架構。
使用鏈接時優化 LTO(Link-Time Optimization)
可以在 Build Setting - Link-Time Optimization 項設置優化方式
其提供三種選項:
-
No 不開啟鏈接期優化;(默認項)
-
Monolithic 生成單個 LTO 文件,每次鏈接重新生成,無緩存高內存消耗,參數 LLVM_LTO=YES;
-
Incremental 生成多個 LTO 文件,增量生成,低內存消耗,參數 LLVM_LTO=YES_THIN;
LTO 能帶來的優化有:
-
將一些函數內聯化:不用進行調用函數前的壓棧、調用函數后的出棧操作,提高運行效率與??臻g利用率;
-
去除了一些無用代碼:如果一段代碼分布在多個文件中,但是從來沒有被使用,普通的 -O3 優化方法不能發現跨中間代碼文件的多余代碼,因此是一個局部優化。但是 Link-Time Optimization 技術可以在 link 時發現跨中間代碼文件的多余代碼;
-
對程序有全局的優化作用:這是一個相對廣泛的概念。舉個例子來說,如果一個 if 方法的某個分支永不可能執行,那么在最后生成的二進制文件中就不應該有這個分支的代碼。
LTO 會降低編譯鏈接的速度,所以建議在打正式包時開啟; 開啟了 LTO 之后,Link Map 的可讀性明顯降低,多出了很多數字開頭的類(LTO 的全局優化導致的),所以如果需要閱讀 Link Map,可以先關閉 LTO;
LTO 雖然是鏈接期優化,但是仍然需要編譯期參與,加入了 LTO 的編譯出來的 .a 本質是 LLVM 的 BitCode,如果使用未開啟 LTO 構建出來的的 .a 直接是機器碼了。直接鏈接是無法完成 LTO 優化的。
開啟 LTO 之后跨編譯單元的重復代碼會被鏈接器單獨生成以 .lto.o 為后綴的目標文件進行鏈接。尤其是對于 Objc Runtime 需要的一些結構, 比如方法簽名的 literal string、protocol 的結構等有比較大的優化。同時開啟 Oz 和 LTO 可以讓外聯函數都只存在一份能夠最大限度的優化安裝包體積(是全局的優化作用,將已經外聯的函數去重)。如果項目中大量的使用了 Protocol 建議還是開啟這個選項。
結論:可將Link-Time Optimization選項由 NO 改為 Incremental 。
語言編譯優化
OC
OC 關于編譯內聯優化的參數位于 Build Settings -> Apple Clang - Code Generation -> Optimization Level ,選項如下:
-
None[-O0]: 編譯器不會優化代碼,意味著更快的編譯速度和更多的調試信息,默認在 Debug 模式下開啟;
-
Fast[-O, O1]: 編譯器會優化代碼性能并且最小限度影響編譯時間,此選項在編譯時會占用更多的內存;
-
Faster[-O2]:編譯器會開啟不依賴空間 / 時間折衷所有優化選項。在此,編譯器不會展開循環或者函數內聯。此選項會增加編譯時間并且提高代碼執行效率;
-
Fastest[-O3]:編譯器會開啟所有的優化選項來提升代碼執行效率。此模式編譯器會執行函數內聯使得生成的可執行文件會變得更大。一般不推薦使用此模式;
-
Fastest Smallest[-Os]:編譯器會開啟除了會明顯增加包大小以外的所有優化選項。默認在 Release 模式下開啟;
-
Fastest, Aggressive Optimization[-Ofast]:啟動 -O3 中的所有優化,可能會開啟一些違反語言標準的一些優化選項。一般不推薦使用此模式。
結論:使用默認配置即可,無需修改。
Swift
Swift 關于編譯內聯優化的參數位于 Build Settings -> Swift Compiler - Code Generation -> Optimization Level ,可選參數如下。
-
No optimization[-Onone]:不進行優化,能保證較快的編譯速度。默認在 Debug 模式開啟;
-
Optimize for Speed[-O]:編譯器將會對代碼的執行效率進行優化,一定程度上會增加包大小。默認在 Release 模式下開啟;
-
Optimize for Size[-Osize]:編譯器會盡可能減少包的大小并且最小限度影響代碼的執行效率。
Optimize for Size 的核心原理是對重復的連續機器指令外聯成函數進行復用,和函數內聯的原理正好相反。因此,將其開啟,能減小二進制的大小,但同時理論上會帶來執行效率的額外消耗,對性能(CPU)敏感的代碼使用需要評估。
具體官方描述可見Code Size Optimization Mode in Swift 4.1
配合其使用的還有Compliation Mode設置,其含有兩個選項
-
Single File:單個文件優化,可以減少增量編譯的時間,并且可以充分利用多核 CPU,并行優化多個文件,提高編譯速度。但是對于交叉引用無能為力;
-
Whole Module:模塊優化,最大限度優化整個模塊,能處理交叉引用。缺點不能利用多核 CPU 的優勢,每次編譯都會重新編譯整個 Module;
在 Relese 模式下 -Osize 和 Whole Module 同時開啟效果會發揮的最好,從現有的案例中可以看到它會減少 5%~30% 的可執行文件大小,并且對性能的影響也微乎其微(大約 5%)。
結論:將 Release 默認下配置改為 Optimize for Size[-Osize],Compliation Mode選項改為Whole Module
死代碼裁剪
可以在 Build Setting - DEAD_CODE_STRIP 項設置。
在構建完成之后如果是 C、C++ 等靜態的語言的代碼、一些常量定義,如果發現沒有被使用到將會被標記為 Dead code。開啟 DEAD_CODE_STRIP = YES 這些 Dead code 將不會被打包到安裝包中。在 LinkMap 這些符號也會被標記為 <<dead>> 。
該項其實也屬于在清除無用代碼。
結論:默認配置即為 YES,所以使用默認配置即可,無需修改。
去除符號信息
可執行文件中的符號是指程序中的所有的變量、類、函數、枚舉、變量和地址映射關系,以及一些在調試的時候使用到的用于定位代碼在源碼中的位置的調試符號,符號和斷點定位以及堆棧符號化有很重要的關系。
Strip Style
Strip Style 表示的是我們需要去除的符號的類型的選項,其分為三個選擇項:
-
All Symbols: 去除所有符號,一般是在主工程中開啟;
-
Non-Global Symbols: 去除一些非全局的 Symbol(保留全局符號,Debug Symbols 同樣會被去除),鏈接時會被重定向的那些符號不會被去除,此選項是靜態庫 / 動態庫的建議選項;
-
Debug Symbols: 去除調試符號,去除之后將無法斷點調試。
結論:主工程選擇All Symbols,靜、動態庫選擇Non-Global Symbols。
Strip Linked Product
并不是所有的符號都是必須的,比如 Debug Map,所以 Xcode 提供給我們 Strip Linked Product 來去除不需要的符號信息 (Strip Style 中選擇的選項相應的符號),去除了符號信息之后我們就只能使用 dSYM 來進行符號化了,所以需要將 Debug Information Format 修改為 DWARF with dSYM file。
需要注意Strip Linked Product 選項在 Deployment Postprocessing 設置為 YES 的時候才生效,而 Deployment Postprocessing 在 Archive 時不受手動設置的影響,會被強制設置成 YES。
結論:將Deployment Postprocessing設置為 NO,將Strip Linked Product設置為YES,將Release模式的下的Debug Information Format 修改為 DWARF with dSYM file。
Strip Debug Symbols During Copy
與 Strip Linked Product 類似,但是這個是將那些拷貝進項目包的三方庫、資源或者 Extension 的 Debug Symbol 去除掉,同樣也是使用的 strip 命令。這個選項不受Deployment Postprocessing的控制,所以我們只需要在 Release 模式下開啟,不然就不能對三方庫進行斷點調試和符號化了。
Cocoapods 管理的動態庫 (use_framework!) 的情況就相對要特殊一點,Cocoapods 中的的動態庫是使用自己實現的腳本 Pods-xxx-frameworks.sh 來實現拷貝的,所以并不會走 Xcode 的流程,當然也就不受 Strip Debug Symbols During Copy 的影響。當然 Cocoapods 是源碼管理的,所以只需要將源碼 Target 中的 Strip Linked Product 手動設置為 YES 即可。
結論:Strip Debug Symbols During Copy在Release 模式下設置為YES,在Debug模式下設置為false。
Strip Swift Symbols
開啟 Strip Swift Symbols 能幫助我們移除相應 Target 中的所有的 Swift 符號,這個選項也是默認打開的。 這一選項是出現 Xcode 將 xcarchive 包導出成 ipa 文件過程中出現的,不是通過Build Setting設置的。
示意圖
結論:一般默認勾選,如果沒勾選請手動勾選。
選項設置方式優化
大部分項目都會使用 Cocoapods 工具進行管理,Cocoapods 的 project 文件在每次 pod install 或者 pod update 會重置,所以需要 hook pod install 來設置 Pods 中每個 Target 的編譯選項。
post_install?do?|installer|installer.pods_project.targets.each?do?|target|target.build_configurations.each?do?|config|config.build_settings['ENABLE_BITCODE']?=?'NO'config.build_settings['STRIP_INSTALLED_PRODUCT']?=?'YES'config.build_settings['SWIFT_COMPILATION_MODE']?=?'wholemodule'if?config.name?==?'Debug'config.build_settings['SWIFT_OPTIMIZATION_LEVEL']?=?'-Onone'config.build_settings['GCC_OPTIMIZATION_LEVEL']?=?'0'elseconfig.build_settings['SWIFT_OPTIMIZATION_LEVEL']?=?'-Osize'config.build_settings['GCC_OPTIMIZATION_LEVEL']?=?'s'endendend endMach-O 可執行文件瘦身
在對 Mach-O 文件進行瘦身優化時,我們可以通過分析 Link Map 文件來給我們一定的數據參考,幫助我們分析 Mach-O 文件的構成。
Link Map 是編譯鏈接時可以生成的一個 txt 文件,它生成目的就是幫助程序員分析包大小。Link Map 記錄了每個方法在當前的二進制架構下占據的空間。通過分析 Link Map,我們可以了解每個類甚至每個方法占據了多少安裝包空間。
開啟 Build setting 中的 Write Link Map File 開關,Xcode 就會生成一份 Link Map 文件。其中生成的 Link Map 文件路徑如下: ~/Developer/Xcode/DerivedData/項目/Build/Intermediates.noindex/項目.build/Debug-iphonesimulator/項目.build/項目-LinkMap-normal-x86_64.txt
如果直接閱讀 Link Map 文件,效率會比較低,也不直觀,我們可以使用一些工具幫助我們分析。
LinkMap 工具地址
LinkMap效果圖
清除無用代碼
通過 AppCode 查找無用代碼
AppCode 提供了非常強大的代碼靜態檢查工具,使用 Inspect Code,可以找到很多代碼優化的地方;
AppCode Inspect
基于源碼掃描
一般都是對源碼文件進行字符串匹配。例如將 A *a、[A xxx]、NSStringFromClass("A")、objc_getClass("A") 等歸類為使用的類,@interface A : B 歸類為 定義的類,然后計算差集。
基于源碼掃描 有個已經實現的工具 -- fui,它的實現原理是查找所有 #import "A" 和所有的文件進行比對。
手動去除
-
已經下線的陳舊代碼,AB 試驗已經下線的代碼;
-
通過轉 H5、Hybrid 或者 RN 實現的 Native 功能,可以定期清理;
-
將部分功能進行重構,以此去除一定的代碼。
多個可執行文件中去除相同代碼
這里的多個可執行文件一般是指 APP 宿主程序與 Extension 程序,如果 APP 宿主程序與 Extension 程序都依賴同一個靜態庫庫時,就會導致兩個可執行文件中都包含相同的代碼;個人覺得有兩種解決方案:
-
考慮到 Extension 程序相對宿主程序來說功能較小,可盡量使用原生功能,不接入三方庫;
-
如果想要接入同一份庫,可將該庫以動態庫的方式引入,最終兩個可執行文件會動態鏈接同一份庫,避免了重復代碼;
結論:根據項目實際情況選用解決方案。
更多優化
Pod
使用 resource_bundles 配合 xcassets 的方式來集成各個插件中的資源文件,因為 resource_bundle 中的資源在構建期能經過 Xcode 的優化,而 resource 中的資源則不能。并且這種形式可以將每個 pod 的資源放在自己的 Bundle 中,更方便管理。
結論:自定義 Pod 如果含有資源,盡量使用 resource_bundle 的方式引用資源。
編碼素質
代碼復用,禁止無腦拷貝代碼,共用代碼下沉為底層組件;
重復功能的框架使用一套;
不要因為一個很小的功能就引入一個框架,或者有類似輕量級框架時轉而選擇一個功能強大但重量級框架;
...
結論:這一部分需要從提升個人編碼素質、團隊文化以及團隊管理等方面入手。
其他
還有一些優化方式,如二進制段壓縮,__TEXT 段遷移等方式,大家可以去尋找相關的資料查看,這里只簡單介紹相關的原理。
-
二進制段壓縮:Mach-O 文件中并不是每個段 / 節在程序啟動的第一時間都要被用到??梢栽跇嫿ㄟ^程中將 Mach-O 文件中的這部分段 / 節壓縮,然后只要在這些段被使用到之前將其解壓到內存中,就能達到了減少包大小的效果,同時也能保證程序正常運行。
-
**TEXT 段遷移:將可執行文件的 **TEXT 段中的部分節移動到其它的段,提高了可執行文件的壓縮效率。具體可見相關鏈接中【今日頭條優化實踐:iOS 包大小二進制優化,一行代碼減少 60 MB 下載大小】;
-
...
最后
本文主要歸納總結了一些常用的瘦身方法,當然不同的項目需求以及業務場景都會產生一些對應的瘦身方法,大家可以根據自己的業務特性去尋找一些更好更優的瘦身技巧。
最后,祝大家周末愉快!
Let's be CoderStar!
相關鏈接
-
我在 Uber 親歷的最嚴重的工程災難
-
iOS 安裝包瘦身實踐
-
今日頭條 iOS 安裝包大小優化 - 新階段,新實踐
-
干貨|今日頭條 iOS 端安裝包大小優化—思路與實踐
-
今日頭條優化實踐: iOS 包大小二進制優化,一行代碼減少 60 MB 下載大小
-
探究 WebP 一些事兒
-
Palette Images
-
iOS PNG 使用指南
-
iOS 減包實戰:Compress PNG Files 作用分析
-
iOS App 瘦身減肥記
-
iOS 安裝包瘦身 (上篇)
-
iOS 安裝包瘦身(下篇)
有一個技術的圈子與一群同道眾人非常重要,來我的技術公眾號,這里只聊技術干貨。
微信公眾號:CoderStar
?
總結
以上是生活随笔為你收集整理的iOS 优化 - 瘦身的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python数字转换成中文大写_pyth
- 下一篇: iOS 6与iOS 7的增量更新的区别