本文是借鑒 戴銘老師 iOS開發高手課 內容總結。
App 的安裝包主要是由資源和可執行文件組成的。
App瘦身:無用圖片、代碼刪除 + 圖片壓縮
目錄
1、蘋果?App Thinning 功能介紹
2、刪除無用圖片 方法?
3、谷歌Webp圖片資源壓縮
4、騰訊公司開發的iSparta? 工具進行圖片壓縮。
5、代碼瘦身:對可執行文件的瘦身方法。(人工查找)
6、LinkMap 結合 Mach-O 找無用代碼
7、通過 AppCode 找出無用代碼
8、通過 ObjC 的 runtime 源碼,我們可以找到怎么判斷一個類是否初始化過的函數
1、充分利用 蘋果官方自帶 App Thinning 功能
1、iOS設備屏幕尺寸、分辨率越來越多,需要更多的資源匹配不同的尺寸和分辨率。而App Thinning會選擇可用于當前設備的資源內容進行下載。iPhone6只會下載2x圖片,iPhone6plus只會下載3x圖片。并且下載適合自己設備的指令集架構文件。
2、App Thinning 有三種方式:
? ? ? ?App Slicing:會在iTunes Connect上傳App后,對App進行切割,創建不同的變體,適用不同的設備
? ? ? ?On-Demand Resources:主要為游戲關卡場景服務的。它會根據用戶的關卡進度下載隨后幾個關卡的資源,并且已經過關的資源也會被刪掉,這樣可以減少初裝App的包大小
? ? ? Bitcode:是針對特定設備進行包大小優化,優化不明顯。
3、使用:Xcode 和 App Store已經幫我們完成大部分工作。我們通過Xcode添加 xcassets目錄,然后將圖片添加到這個目錄既可。添加2x 和 3x分辨率圖片。
2、無用圖片資源優化:刪除無用圖片 和 圖片資源壓縮
1、刪除無用圖片:?
? ? ? ?1> 通過find 命令獲取App安裝包中所有的資源文件。比如 find /Users/..../工程文件
? ? ? ?2>設置用到的資源的類型,比如jpg、gif、png、webp
? ? ? ?3>使用正則匹配在源碼中找出使用到的資源名,比如 pattern = @"@"(.+?)""
? ? ? ?4>使用find命令找到的所有資源文件,再去掉代碼中使用到的資源文件,剩下的就是無用資源了
? ? ? ?5>對于按照規則設置的資源名,我們需要在匹配使用資源的正則表達式里添加相應的規則,比如 @"imgge_%d"
? ? ? ?6>確認無用資源后,就可以對這些無用資源執行刪除操作了。這個刪除操作,你可以使用NSFileManager系統類提供的功能來完成。
2、使用開源的工具來完成以上操作:LSUnusedResources??特別是對于使用編號規則的圖片來說,可以通過直接添加規則來處理。使用方式也很簡單
3、谷歌Webp圖片資源壓縮:對于 App 來說,圖片資源總會在安裝包里占個大頭兒。對它們最好的處理,就是在不損失圖片質量的前提下盡可能地作壓縮。目前比較好的壓縮方案是,將圖片轉成 WebP。WebP 是 Google 公司的一個開源項目。
1、WebP特點:
? ? ? ? ? 1>WebP 壓縮率高,而且肉眼看不出差異,同時支持有損和無損兩種壓縮模式。比如,將 Gif 圖轉為 Animated WebP ,有損壓縮模式下可減少 64% 大小,無損壓縮模式下可減少 19% 大小。
? ? ? ? ? 2>WebP 支持 Alpha 透明和 24-bit 顏色數,不會像 PNG8 那樣因為色彩不夠而出現毛邊。
2、圖片轉成 WebP 操作:
Google 公司在開源 WebP 的同時,還提供了一個圖片壓縮工具 cwebp?來將其他圖片轉成 WebP。cwebp 使用起來也很簡單,只要根據圖片情況設置好參數就行。
cwebp 語法如下:cwebp [options] input_file -o output_file.webp
比如,你要選擇無損壓縮模式的話,可以使用如下所示的命令:cwebp -lossless original.png -o new.webp
其中,-lossless 表示的是,要對輸入的 png 圖像進行無損編碼,轉成 WebP 圖片。不使用 -lossless ,則表示有損壓縮。
在 cwebp 語法中,還有一個比較關鍵的參數 -q float。圖片色值在不同情況下,可以選擇用 -q 參數來進行設置,在不損失圖片質量情況下進行最大化壓縮:
? ? ? ? ? ? ? ? ? 小于 256 色適合無損壓縮,壓縮率高,參數使用 -lossless -q 100;
? ? ? ? ? ? ? ? ? 大于 256 色使用 75% 有損壓縮,參數使用 -q 75;
? ? ? ? ? ? ? ? ? 遠大于 256 色使用 75% 以下壓縮率,參數 -q 50 -m 6。
4、騰訊公司開發的iSparta? 工具進行圖片壓縮。
1、優點:iSpart 是一個 GUI 工具,操作方便快捷,可以實現 PNG 格式轉 WebP,同時提供批量處理和記錄操作配置的功能。如果是其他格式的圖片要轉成 WebP 格式的話,需要先將其轉成 PNG 格式,再轉成 WebP 格式。
圖片壓縮完了并不是結束,我們還需要在 ?「顯示圖片」時使用 libwebp 進行解析。這里有一個 iOS 工程使用 ?libwebp 的范例,你可以點擊 這個鏈接?查看。或者使用?pod 'SDWebImageWebPCoder' ?框架或者 ?YYimage 框架。Webp圖片加載使用方法
2、缺點:WebP 在 CPU 消耗和解碼時間上會比 PNG 高兩倍。所以,我們有時候還需要在性能和體積上做取舍。
? ? ? 建議:如果圖片大小超過了 100KB,你可以考慮使用 WebP;而小于 100KB 時,你可以使用網頁工具 TinyPng?或者 GUI 工具 ImageOptim?進行圖片壓縮。這兩個工具的壓縮率沒有 WebP 那么高,不會改變圖片壓縮方式,所以解析時對性能損耗也不會增加。
5、代碼瘦身:對可執行文件的瘦身方法。
1、可執行文件就是 Mach-O 文件,其大小是由代碼量決定的。通常情況下,對可執行文件進行瘦身,就是 ?「找到并刪除無用代碼」的過程。
2、思路:
? ? ? ? ? ?1>首先,找出方法和類的全集;
? ? ? ? ? ?2>然后,找到使用過的方法和類;
? ? ? ? ? ?3>接下來,取二者的差集得到無用代碼;
? ? ? ? ? ?4>最后,由人工確認無用代碼可刪除后,進行刪除即可。
6、LinkMap 結合 Mach-O 找無用代碼
1、怎么快速找到方法和類的全集:我們可以通過分析 LinkMap 來獲得所有的代碼類和方法的信息。獲取 LinkMap 可以通過將 Build Setting 里的 Write Link Map File 設置為 Yes,然后指定 Path to Link Map File 的路徑就可以得到每次編譯后的 LinkMap 文件了。
2、LinkMap 文件分為三部分:Object File、Section 和 Symbols。
其中:1>Object File 包含了代碼工程的所有文件;
? ? ? ? ? ? 2>Section 描述了代碼段在生成的 Mach-O 里的偏移位置和大??;
? ? ? ? ? ? 3>Symbols 會列出每個方法、類、block,以及它們的大小。
通過 LinkMap ,你不光可以統計出所有的方法和類,還能夠清晰地看到代碼所占包大小的具體分布,進而有針對性地進行代碼優化。
3、得到了代碼的全集信息以后,我們還需要找到已使用的方法和類,這樣才能獲取到差集,找出無用代碼。所以接下來,我們要 通過 Mach-O 取到使用過的方法和類。
iOS 的方法都會通過 objc_msgSend 來調用。而,objc_msgSend 在 Mach-O 文件里是通過 __objc_selrefs 這個 section 來獲取 selector 這個參數的。
所以,__objc_selrefs 里的方法一定是被調用了的。__objc_classrefs 里是被調用過的類,__objc_superrefs 是調用過 super 的類。通過 __objc_classrefs 和 __objc_superrefs,我們就可以找出使用過的類和子類。那么,Mach-O 文件的 __objc_selrefs、__objc_classrefs 和 __objc_superrefs 怎么查看呢?
我們可以使用 MachOView?這個軟件來查看 Mach-O 文件里的信息。MachOView 同時也是一款開源軟件,如果你對源碼感興趣的話,可以點擊這個地址查看。
具體的查看方法,我將通過一個案例和你展開。
? ? ? ? ? ? ? ? ?1>首先,我們需要編譯一個 App。
? ? ? ? ? ? ? ? ?2>然后,將生成的 DemoXXX.app 包解開,取出DemoXXX。
? ? ? ? ? ? ? ? ?3>最后,我們就可以使用 MachOView 來查看 Mach-O 里的信息了。
可以看到 __objc_selrefs、__objc_classrefs 和、__objc_superrefs 這三個 section。但是,這種查看方法并不是完美的,還會有些問題。原因在于, Objective-C 是門動態語言,方法調用可以寫成在運行時動態調用,這樣就無法收集全所有調用的方法和類。所以,我們通過這種方法找出的無用方法和類就只能作為參考,還需要二次確認。
(缺點:這里只能看到已調用的方法,得去工程里查找為調用的方法,比較麻煩。建議結合 下面 的APPCode方法來進行過濾 找到 未使用的方法、類)
7、通過 AppCode 找出無用代碼(當代碼量過百萬行時 AppCode 的靜態分析會“歇菜”。但是,如果工程量不是很大的話,我還是建議你直接使用 AppCode 來做分析。)
1、AppCode 做分析的方法很簡單,直接在 「AppCode 里選擇 Code->Inspect Code 」就可以進行靜態分析。
2、接下來,說一下這些無用代碼的主要類型。
? ? ? ? 1>無用類:Unused class 是無用類,Unused import statement 是無用類引入聲明,Unused property 是無用的屬性;
? ? ? ? ?2>無用方法:Unused method 是無用的方法,Unused parameter 是無用參數,Unused instance variable 是無用的實例變量,Unused local variable 是無用的局部變量,Unused value 是無用的值;
? ? ? ? ?3>無用宏:Unused macro 是無用的宏。
? ? ? ? ?4>無用全局:Unused global declaration 是無用全局聲明。?
【這里結合 上面工具 來進行篩選 ——> 沒有用到的無用類、方法?!浚?!
3、APPCode靜態檢查的問題:
? ? ? ? ?1>JSONModel 里定義了未使用的協議會被判定為無用協議;
? ? ? ? ? 2>如果子類使用了父類的方法,父類的這個方法不會被認為使用了;
? ? ? ? ? 3>通過點的方式使用屬性,該屬性會被認為沒有使用;使用 performSelector 方式調用的方法也檢查不出來,比如 self performSelector:@selector(arrivalRefreshTime);?
? ? ? ? ? ?4>運行時聲明類的情況檢查不出來。比如通過 NSClassFromString 方式調用的類會被查出為沒有使用的類,比如 layerClass = NSClassFromString(@“SMFloatLayer”)。還有以[[self class] accessToken] 這樣不指定類名的方式使用的類,會被認為該類沒有被使用。像 UITableView 的自定義的 Cell 使用 registerClass,這樣的情況也會認為這個 Cell 沒有被使用。
基于以上種種原因,使用 AppCode 檢查出來的無用代碼,還需要人工二次確認才能夠安全刪除掉。
4、即使你使用 LinkMap 結合 Mach-O 或者 AppCode 的方式,通過靜態檢查已經找到并刪除了無用的代碼,那么就能說包里完全沒有無用的代碼了嗎?
實際上,在 App 的不斷迭代過程中,新人不斷接手、業務功能需求不斷替換,會留下很多無用代碼。這些代碼在執行靜態檢查時會被用到,但是線上可能連這些老功能的入口都沒有了,更是沒有機會被用戶用到。也就是說,這些無用功能相關的代碼也是可以刪除的。
8、通過 ObjC 的 runtime 源碼,我們可以找到怎么判斷一個類是否初始化過的函數
1、isInitialized 的結果會保存到元類的 class_rw_t 結構體的 flags 信息里,flags 的 1<<29 位記錄的就是這個類是否初始化了的信息。而 flags 的其他位記錄的信息,你可以參看 objc runtime 的源碼,
2、lags 采用位方式記錄布爾值的方式,易于擴展、所用存儲空間小、檢索性能也好。所以,經常閱讀優秀代碼,特別有助于提高我們自己的代碼質量。
既然能夠在運行中看到類是否初始化了,那么我們就能夠找出有哪些類是沒有初始化的,即找到在真實環境中沒有用到的類并清理掉。
總結:對于上線時間不長的新 App 和那些代碼量不大的 App 來說,做些資源上的優化,再結合使用 AppCode 就能夠有很好的收益。而且把這些流程加入工作流后,日常工作量也不會太大。但是,對于代碼量大,而且業務需求迭代時間很長的 App 來說,包大小的瘦身之路依然任道重遠,這個領域的研究還有待繼續完善。LinkMap 加 Mach-O 取差集的結果也只能作為參考,每次人工確認的成本是非常大的,只適合突擊和應急清理時使用。最后日常采用的方案,可能還是用運行時檢查類的方式,這種大粒度檢查的方式精度雖然不高,但是人工工作量會小很多。