本文來自網易雲社區html
做者:饒夢雲ios
Activating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to turn on dead code stripping. Remove functions and data that are unreachable by the entry point or exported symbolsgit
Xcode 默認會開啓此選項,C/C++/Swift 等靜態語言編譯器會在 link 的時候移除未使用的代碼,可是對於 Objective-C 等動態語言是無效的。由於 Objective-C 是創建在運行時上面的,底層暴露給編譯器的都是 Runtime 源碼編譯結果,全部的部分應該都是會被判別爲有效代碼。github
掃描無用代碼的基本思路都是查找已經使用的方法/類和全部的類/方法,而後從全部的類/方法當中剔除已經使用的方法/類剩下的基本都是無用的類/方法,可是因爲 Objective-C 是動態語言,可使用字符串來調用類和方法,因此檢查結果通常都不是特別準確,須要二次確認。目前市面上的掃描的思路大體能夠分爲 3 種:swift
基於 Clang 掃描xcode
基於可執行文件掃描緩存
基於源碼掃描安全
基本思路是基於 clang AST。追溯到函數的調用層級,記錄全部定義的方法/類和全部調用的方法/類,再取差集。具體原理參考 如何使用 Clang Plugin 找到項目中的無用代碼,目前只有思路沒有現成的工具。微信
Mach-O 文件中的 (__DATA,__objc_classlist) 段表示全部定義的類, (__DATA.__objc_classrefs) 段表示全部引用的類(繼承關係是在 __DATA.__objc_superrefs 中);使用的方法和引用的方法也是相似原理。所以咱們使用 otool 等命令逆向可執行文件中引用到的類/方法和全部定義的類/方法,而後計算差集。具體參考iOS微信安裝包瘦身,目前只有思路沒有現成的工具。app
通常都是對源碼文件進行字符串匹配。例如將 A *a、[A xxx]、NSStringFromClass("A")、objc_getClass("A") 等歸類爲使用的類,@interface A : B 歸類爲定義的類,而後計算差集。
基於源碼掃描 有個已經實現的工具 - fui,可是它的實現原理是查找全部 #import "A" 和全部的文件進行比對,因此結果相對於上面的思路來講可能更不許確。
AppCode 提供了 Inspect Code 來診斷代碼,其中含有查找無用代碼的功能。
它能夠幫助咱們查找出 AppCode 中無用的類、無用的方法甚至是無用的 import ,可是沒法掃描經過字符串拼接方式來建立的類和調用的方法,因此說仍是上面所說的 基於源碼掃描 更加準確和安全。
經過掃描的方式去檢查無用代碼有個痛點就是 類的方法調用是一種引用關係,以上所說的四種思路都是查找到引用末端的未使用的代碼,咱們很難經過一次掃描就定位到全部未使用的類,自動化實現起來也較難。舉個例子來講,假如 A 是一個未使用到的類,可是 A 引用了 B,因此首次檢查結果是 A 未被引用,B 被無用類 A 引用了,咱們須要把 A 刪除了以後咱們才能瞭解到 B 是不是無用的類。固然若是你從新去實現一個引用樹的話就另當別論了。
因爲掃描無用類實現起來較爲麻煩,而且其檢查結果也不是特別準確。因此建議仍是讓開發者養成一個良好的習慣,在迭代或者重構代碼的時候把老的代碼刪除,不要等到量變引發質變的時候纔回頭去優化。
重複代碼堆積太多,不只意味着 Bad Code Smell,咱們的包大小也會受到影響,咱們可使用 PMD 來檢查項目中的重複代碼,而且作選擇性的重構。
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' else config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize' config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 's' end end endend
本文中的資源分爲圖片資源、音頻資源、視頻資源、視圖資源、字體資源、網頁資源等等。資源都是弱類型的,會隨着項目工程的增加而持續增加,維護起來費時費力,因此通常都是但願能有工具化、自動化的解決方案。
Xcode 提供的給咱們兩個編譯選項來幫助壓縮 PNG 資源:
Compress PNG Files:打包的時候自動對圖片進行無損壓縮,使用的工具爲 pngcrush,壓縮比仍是至關高的,比較流行的壓縮軟件 ImageOptim 也是使用 pngcrush 進行壓縮 PNG 的。
Remove Text Medadata From PNG Files:能幫助咱們移除 PNG 資源的文本字符,好比圖像名稱、做者、版權、創做時間、註釋等信息。
項目引進的 PNG 資源都是自動被 Xcode 進行壓縮了,因此徹底不須要本身再去用工具壓縮一遍。當除非你是使用 bundle 管理的資源,由於 bundle 是直接拷貝進項目,並不會被 Xcode 進行壓縮;JPG 或者其餘類型的圖片資源可使用 ImageOptim 進行無損壓縮而後導入到 Xcode 中,爲了提升效率建議仍是提供 PNG 格式的圖片。
iOS 9 中引入的 App Thinning 中提到過 Slicing 的技術,當咱們把一個完整的安裝包提交給 App Store 後,App Store 會爲不一樣的設備準備不一樣的變體(Variant),設備的在下載 App 的時候它能幫助咱們自動選擇合適的 Variant 進行下載。
可執行文件的 Slicing 技術就是上面所說的 BitCode,一樣資源文件也是支持 Slicing 的。好比 iPhone 6 下載的安裝包中就只會包含 2x 圖,iPhone 6 Plus 下載的安裝包就只會包含 3x 圖,可是隻有使用 asset catelogs(也就是 XCAssets) 管理的資源才支持 Slicing,因此儘可能仍是使用 XCAsset 來管理資源圖片。同時 XCAsset 也支持 PDFs 矢量圖,在上傳到 App Store 以後,會根據矢量圖自動生成 1x, 2x, 3x 圖,而後進行 Slicing。
固然 XCAsset 也有它的存在的問題:
使用 XCAsset 管理的資源會被壓縮並打包成一個 Asset.car 文件,咱們沒法獲取相應圖片的物理路徑,所以咱們沒法使用 [UIImage imageWithContentsOfFile:] 的方式來獲取圖片。對於那些須要使用物理路徑的方式來訪問的圖片,建議仍是直接拖拽到 App 中進行管理。
iOS 10.3 推出的更換 App Icon 的資源文件只能放在 App 根目錄下進行管理。
使用 XCAsset 管理圖片後,Xib/Storyboard 中設置的帶後綴 .png 圖片在 Interface Builder 是不可見的,都是顯示的問號,可是運行起來是沒有問題的。最好的作法是全局搜索並去掉後綴保證更好的開發體驗。
未使用的資源可使用腳原本進行刪除。強烈推薦使用 FengNiao 來自動刪除圖片,由於其相對比較新,是 2017 年開始開發的,而且是使用 swift 語言開發的,方便進行二次開發。FengNiao 的基本原理是查找出項目中全部使用到的字符串和項目中全部的資源文件。二者進行匹配(徹底匹配和模式匹配,模式匹配支持帶數字資源的前綴/中綴/後綴匹配),計算差集就爲未使用的資源。
相比於以前流行的 LSUnusedResources,FengNiao 支持模式匹配會更增強大:好比咱們導入 image_01 image_02 image_03 這樣的圖片資源做爲幀動畫素材,使用的時候是 image_%d 或者 image_\(index) 方式,FengNiao 會把這些圖片資源做爲使用中的資源,不會出現誤刪的狀況。固然若是你仍是用了其餘 Pattern,能夠考慮擴展 FengNiao。
除了這些以外,FengNiao 是命令行工具,咱們能夠給 Xcode 添加 Run Script,在每次構建的時候自動檢測/清理未使用的資源。
因爲基於源碼的掃描工具結果不是百分百準確的,因此建議最好的作法是在項目編譯的時候提供出顯式的 Warning,而後再次確認以後再去刪除。同時也能夠配合資源命名規範來優化工具,若是大家的命名規範和工具的檢測規範可以保持一致的話,搜索的結果無疑是最爲準確的。
之因此要使用自動化工具來檢測重複資源的緣由是由於資源是弱類型,咱們在項目迭代過程中手動去維護是至關麻煩的一個過程。轉換一下思惟,若是資源變成強類型了,那咱們維護起來就至關容易了。目前就有這樣一個工具-R.swift,相似於 Android 開發中的 R 文件,有興趣的能夠去嘗試。
這裏所說的重複資源是資源內容相同可是命名不相同的一些資源,對於此類資源,咱們可使用 fdupes 來進行掃描並去除,fdupes 的原理是對比不一樣文件的簽名,簽名相同的文件就會斷定爲重複資源。
而後咱們就能夠在 Xcode 中添加 Run Script,對於掃描到的相同的資源,咱們能夠顯式的報出 Warning,而後咱們在開發階段解決資源重複的問題。
On-Demand Resources 是 Apple 在 iOS 9 跟 App Thinning 一塊兒引進的一項減小安裝包體積技術,大體的概念是蘋果幫你把全部 App 中的資源管理在 App Store 雲端上,而後你須要把資源標記爲不一樣的 Tag,須要的時候纔去下載相應 Tag 的圖片。引用蘋果文檔中的一張圖表示。
這種機制對於許多圖片資源都放在本地的 App 就會比較有用,好比遊戲中的不一樣關卡能夠分爲不一樣的 tag,在用戶通關了一關以後才下載下一關資源。
視頻/音頻等圖片資源相對圖片來講會大不少,因此建議把視頻/音頻放在服務端,客戶端在使用的時候進行下載或者使用流播放。
H5 資源也是建議放在服務端,若是對 H5 加載和離線訪問有要求的話,可使用離線緩存的方式來緩存網頁資源到本地。
這裏所說的視圖資源是指 xib/storyboard。xib 在打包時會被壓縮爲 nib 文件,storyboard 文件會被壓縮爲 storyboardc 文件,storyboardc 是個壓縮包,內部包含了相應的 nib 和 一個 plist 文件。通常的 nib 文件壓縮後在幾 KB 到幾十 KB 大小,這部分包大小的影響相對於 xib 能提升開發效率來講影響是微乎及微的,網易漫畫 App 中使用到了 257 個 xib 文件,可是其在 payload 中的數據僅僅只有 1.7M 大小。
Framework 文件夾存放的是 Embedded Framework,它在打包的時候最終會被拷貝進 Target App Bundle 中的 Framework 文件夾中,在 App 啓動的時候纔會被連接和加載。Embedded Framework 主要分類兩類:
SwiftSupport:Framework 文件夾中前綴是 libSwift 的一些 framework。因爲目前 Swift ABI 還未穩定,咱們發佈應用的時候還須要帶上一份本身應用中使用到的 Swift 標準庫代碼,這部分佔用最終 ipa 的大小可能在 10M 左右。
其餘依賴庫:使用 Cocoapods 管理依賴而且設置了 user_framework! 時三方庫源碼都會打包成 framework,而後導入到工程當中。
這裏所說的 Framework 表示的是: 靜態庫(.a) Framework(Static Library)
目前絕大部分的 Framework 的作法是直接將資源放進 bundle 中進行管理的,在主工程打包的時候,Xcode 會將這部分資源直接拷貝進 App Target Bundle 中,這樣作就存在2個問題:
使用 bundle 管理的資源是不會被 Xcode 優化的(圖片壓縮等)
使用 bundle 管理的資源不享受 App Thinning/Slicing。
因此儘可能仍是選擇 XCAsset 進行 Framework 的資源管理,靜態庫和動態庫的管理方式有所不一樣:
靜態庫(.a)/Framework(Static Library): 靜態庫的目標文件(.a/.framework) 中是不能包含資源文件的,因此這部分只能使用 bundle 來管理。可是因爲 bundle 直拷貝的特性,咱們須要把 xib/storyboard/asset catalog 編譯後的產物(nib/storyboardc/Asset.car)放進 bundle 裏。比較廣泛的一個作法是藉助 Bundle Target 來編譯咱們的資源文件,具體作法看這篇文章。
動態庫: 動態庫相對來講要簡單一點,由於動態庫自己就是一個 bundle。因此咱們直接把資源文件放在目標文件(.framework)中就能夠了。
若是你是使用 Cocoapods 管理你的源碼,也可使用 XCAsset 來管理資源,參考 在 Cocoapods 中使用 XCAsset。
這部分能夠參考以上的可執行文件瘦身。
Plugin 內部主要存放的就是 App Extension,App Extension 是獨立打包簽名,而後再拷貝進 Target App Bundle 的。
靜態庫最終會打包進可執行文件內部,因此若是 App Extension 依賴了三方靜態庫,同時主工程也引用了相同的靜態庫的話,最終 App 包中可能會包含兩份三方靜態庫的體積。
動態庫是在運行的時候才進行加載連接的,因此 Plugin 的動態庫是能夠和主工程共享的,把動態庫的加載路徑 Runpath Search Paths 修改成跟主工程一致就能夠共享主工程引入的動態庫。
在 Swift ABI 穩定以前,Swift 標準庫會被拷貝進 App 當中。Swift 標準庫是動態連接庫,也是能夠在主工程和其餘的 App Extensions 之間共享的,前提固然是全部 Target 使用的 Swift 版本是一致的,不然就會出現意料以外的 bug。 設置共享分爲兩步:
設置 Extension 中的 Always Embed Swift Standard Libraries 爲 NO,讓編譯器再也不爲 Extension 生成 Swift 標準庫
設置 Extension 中的動態庫的查找路徑爲主工程的 Framework 文件夾
咱們在網易漫畫 App 中逐漸進行了實踐,這次主要進行的是可執行文件的瘦身:編譯優化以及去除符號;資源瘦身:冗餘資源清除以及主工程圖片分片(App Thinning)。下面是優化先後的部分數據:
優化前 | 優化後 | 效果 | |
---|---|---|---|
Executable | 35.4M | 17.1M | 52.1% |
Embedded Framework(除去 Swift STL) | 27.8M | 19M | 31.7% |
Images(FengNiao) | 13.3M | 11.7M | 12% |
Executable 的數據顯示的比較誇張的主要緣由是咱們在作瘦身的時候同時去除了以前使用到的直播 SDK,實際可執行文件的提高效果應該也在 30% 左右。IPA 文件從最初的 70+M 到如今 39.4M,總的來講效果仍是至關明顯。
固然以上並不是這次瘦身的所有內容,Framework 瘦身,冗餘代碼清除等比較難的點後續也會陸續展開實踐,同時也會在本文中進行更新。
將 Build Settings -> Clang/LLVM Generate Code -> Optimize Level 設置爲 Fastest, Smallest(-Os)。
將 Build Settings -> Swift/LLVMGenerate Code -> Optimize Level 設置爲 Optimize for Size(-Osize)。
將 Build Settings -> Strip Linked Product 和 Strip Swift Symbols 設置爲 YES,Deployment Postprocessing 設置爲 NO,發佈代碼的時候也須要勾選 Strip Swift Symbols。
Strip Debug Symbols During Copy 在 Release 模式下設置爲 YES。
有條件的話,適配 BitCode。
不管在主工程或者 Framework 中都使用 XCAsset 來管理資源。
使用工具掃描刪除無用資源,推薦選擇 FengNiao,並添加 Run Scripts。
使用工具掃描重複資源,推薦選擇 fdupes,並添加 Run Scripts。
若是你大量資源都放在本地,推薦使用 On-Demand Resources 方式來管理資源。
在 Swift ABI 穩定以前 Extension 和主 App 之間共享 Swift Standard Libraries。
開啓 Compress PNG Files/Remove Text Metadata From PNG Files。
將 Dead Code Stripping 設置爲 YES。
使用工具掃描和清理無用代碼,同時養成良好習慣,在迭代或者重構的時候刪除舊的代碼。
使用工具掃描重複代碼並重構它。
視頻/音頻/H5 等資源遠端化。
使用 xib/storyboard 來開發視圖界面會必定程序增長安裝包的大小。
使用 Swift 來開發程序會必定程序增長安裝包的大小,對包大小有嚴格要求的話也能夠衡量是否使用 Swift。
若是你對包大小有嚴格要求的話,選擇合適大小的三方庫來進行開發。
本文來自網易雲社區,經做者饒夢雲受權發佈
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
相關文章:
【推薦】 玩轉數據產品設計-小屏幕下的大數據
【推薦】 Innodb實踐總結(一)
【推薦】 年輕設計師如何作好商業設計