分享一款用於分析iOSipa包的腳本工具,使用此工具可以自動掃描發現可修複的包體積問題,同時可以生成包體積數據用於查看。這塊工具我們團隊內部已經使用很長一段時間,希望可以幫助到更多的開發同學更加效率的優化包體積問題。 ...
介紹
分享一款用於分析iOS
ipa包的腳本工具,使用此工具可以自動掃描發現
可修複的包體積問題,同時可以生成包體積數據用於查看。這塊工具我們團隊內部已經使用很長一段時間,希望可以幫助到更多的開發同學更加效率的優化包體積問題。
背景
APPAnalyze
工具最早誕生主要是為瞭解決以下包體積管理的問題:
對於定位下沉市場的APP
來講,包體積是一個非常重要的性能指標,包體積過大會影響用戶下載APP
的意願。但是在早期我們缺少一些手段幫助我們更高效的去進行包體積管理。
自動發現問題
-
提升效率
- 人工排查問題效率低,對於常見的問題儘可能自動掃描出來。並且對於組件化
工程來講,很多外部組件是通過Framework
方式提供,沒有倉庫源碼許可權用於分析包體積問題。 -
流程化
- 形成自動化的質量流程,添加到CI流水線
自動發現包體積問題。
數據指標量化
-
包體積問題
- 提供數據化平臺查看每個組件的包體積待修複
問題 -
包體積大小
- 提供數據化平臺查看每個組件的包體積占比,包括總大小
,單個文件二進位大小
和每個資源大小
。可以針對不同的APP
版本進行組件化粒度的包體積數據對比,更方便查看每個版本的組件大小增量。
實現方式
我們選擇了不依賴源碼而是直接掃描二進位庫的方式來實現這個能力,總體的執行流程一下:
提示:基於組件化工程的掃描方式內部支持,只是暫時不對外開放。
使用指南
安裝
無需安裝。通過下載鏈接直接下載終端可執行命令文件APPAnalyzeCommand
到本地即可使用。
使用
$ /Users/Test/APPAnalyzeCommand --help
OPTIONS:
--version <version> 當前版本 1.0.0
--output <output> 輸出文件目錄。必傳參數
--config <config> 配置JSON文件地址。非必傳參數
--ipa <ipa> ipa.app文件地址。必傳參數
-h, --help Show help information.
執行
打開終端程式直接執行以下shell
指令,即可生成ipa
的包體積數據以及包體積待修複問題。
提示:不能直接使用
AppStore
的包,AppStore
的包需要砸殼。建議儘量使用XCodeDebug
的包。
/Users/Test/APPAnalyzeCommand --ipa ipas/JDAPP/JDAPP.app --output ipas/JDAPP
提示:如果提示
permission denied
沒有許可權,執行sudo chmod -R 777 /Users/a/Desktop/ipas/APPAnalyzeCommand
即可。
生成產物
指令執行完成以後,會在ouput
參數指定的文件夾生成APPAnalyze
文件夾。具體文件介紹如下:
包體積信息
app_size.html
- 展示ipa
每個framework
的包體積數據,可直接用瀏覽器打開。
提示:按照主程式和動態庫進行粒度劃分
framework_size.html
- 展示單個framework
所有的包體積數據,二級頁面不要直接打開
。
提示:
XCode
生成Assets.car
時會將一些小圖片拼接成一張PackedAssetImage
的大圖片。
package_size.json
-ipa
包體積 JSON 數據
包體積待修複問題
app_issues.html
- 展示ipa
每個framework
的包體積待修複問題數量,可直接用瀏覽器打開。
提示:按照主程式和動態庫進行粒度劃分
framework_issues.html
- 展示單個framework
所有的待修複問題詳細數據,二級頁面不要單獨打開
。
issues.json
-ipa
待修複包體積問題 JSON 數據
提示:
json
數據可用於搭建自己的數據平臺,擴展更多的能力。例如查看不同APP版本以及支持多個APP版本對比等。
規則介紹
包體積
未使用的類
定義了類沒有被使用到,包含ObjC
類和Swift
類。
掃描規則
-
沒有查到到對應的
ObjC
類被引用 -
沒有被當做父類使用
-
沒有使用的字元串和類名一致
-
沒有被當做屬性類型使用
-
沒有被創建或調用方法
-
沒有實現
+load
方法
可選的修複方式
-
移除未使用的類
-
Swift
類如果只是用了static
方法考慮修改成Enum
類型 -
如果只是在類型轉換時使用了也會檢測出是未使用的類,例如
(ABCClass *)object;
。建議檢查是否真的有沒有到相關類後刪除 -
對於
ObjC
,如果只是作為方法參數類型使用也會被檢測出是未使用的類。建議刪除相關方法即可。
提示:刪除類相對是一種安全的行為,因為刪除後如果有被使用到會產生編譯時錯誤。雖然有做字元串調用的掃描過濾,不過還是建議檢查是否可能被
Runtime
動態創建調用
未使用的ObjC協議
定義了ObjC
協議沒有被類使用
掃描規則
- 對應的協議沒有被類引用
可選的修複方式
- 移除未使用的協議
Bundle內多Scale圖片
Bundle
內同一張圖片包含多個Scale
會導致更大的包體積。
掃描規則
- 同一個
Bundle
記憶體在同名但是scale
不同的圖片。例如[email protected]
/[email protected]
可選的修複方式
- 移除
Scale
更低的圖片
大資源
文件大小超過一定大小的即為大資源,預設為20KB
。
掃描規則
- 某個文件超過設置的大資源限額
可選的修複方式
-
移除資源動態下發
-
使用更小的數據格式,例如使用更小的圖片格式
重覆的資源文件
存在多個同樣的重覆文件。
掃描規則
- 多個文件
MD5
一致即判定為重覆文件。
可選的修複方式
- 移除多餘的文件
未使用的類Property屬性
ObjC
類中定義的屬性沒有被使用到。
掃描規則
-
對應的屬性沒有被調用 set/get 方法,同時也沒有被
_
的方式使用 -
不是來自實現協議的屬性
-
不是來自
Category
的屬性 -
不存在字元串使用和屬性名一致
可選的修複方式
-
移除對應的屬性
-
如果是介面協議的屬性,需要添加類實現此介面
註意事項
- 可能存在部分動態使用的場景,需要進行一定的檢查。例如一些繼承
NSObject
的數據模型類,可能存在屬性沒有被直接使用到,但是可能會被傳喚成JSON
作為參數的情況。例如後臺下發的數據模型
未使用的ImageSet/DataSet
包含的Imageset
/DataSet
並沒有被使用到。
掃描規則
- 未檢測到和
Imageset
同樣名字的字元串使用
可選的修複方式
- 移除ImageSet/DataSet
註意事項
-
某些
Swift
代碼中使用的字元串不能被髮現所以會被當做未使用。 -
使用字元串拼接的名字作為imageset的名字。
-
被合成到
PackedAssetImage
里的Imageset
不能被掃描出來
未使用的ObjC方法
定義的ObjC
Category 方法並未被使用到。
掃描規則
-
不存在和此方法一樣的方法名使用
-
不存在使用的
字元串
和方法名一致 -
不是來自父類或
Category
的方法 -
不是來自實現介面的方法
-
不是屬性 set/get 方法
可選的修複方式
- 移除對應方法
未使用的分類方法
定義的ObjC
Category 方法並未被使用到。
掃描規則
-
不存在和此方法一樣的方法名使用
-
不存在和方法名一致的
字元串
使用 -
不是來自父類或
Category
的方法 -
不是來自實現介面的方法
可選的修複方式
-
移除未使用的方法
-
如果是介面協議的方法,需要添加類實現此介面
未使用的資源文件
包含的文件資源並沒有被使用到。這裡的資源不包含Imageset
/DataSet
。
掃描規則
- 未檢測到和文件名同樣名字的字元串使用
可選的修複方式
- 移除資源
註意事項
-
某些
Swift
代碼中使用的字元串不能被髮現所以會被當做未使用 -
使用字元串拼接的名字作為資源的名字
安全
動態反射調用ObjC類
存在類名和字元串一致,可能使用NSClassFromString()
方法動態調用類。當字元串
或類名變更時無法利用編譯時檢查發現問題,可能會導致功能異常。
掃描規則
- 存在使用的
字元串
和NSObject子類
類名相同
可選的修複方式
-
使用
NSStringFromClass()
獲取類名字元串 -
使用
Framework
外部的類應該使用方法封裝,除了少部分功能不應該使用反射去調用類
提示:包含繼承
NSObject
的 swift 類。
ObjC屬性記憶體申明錯誤
一些特殊的NSObject
類型的屬性記憶體類型申明錯誤,可能會導致功能異常或觸發Crash
。
掃描規則
-
NSArray
/NSSet
/NSDictionary
類型的屬性使用strong
申明 -
NSMutableArray
/NSMutableSet
/NSMutableDictionary
類型的屬性使用copy
申明
可選的修複方式
- 修改
strong
/copy
申明
衝突的分類方法
ObjC
同一個類的多個Category
分類中存在多個相同的方法,由於運行時最終會載入方法可能是不確定的,可能會導致功能異常等未知的行為。
掃描規則
NSObject類
的多個Category
分類中存在多個相同的方法
修複方式
- 移除多餘的分類方法
重覆的分類方法
ObjC
原始類和類的Category
分類中有相同的方法,分類中的方法會覆蓋原始類的方法,可能會導致功能異常等未知的行為。
掃描規則
NSObject
原始類和類的Category
分類中有相同的方法
修複方式
- 移除重覆的分類方法
未實現的ObjC協議方法
類實現了某個ObjC
協議,但是沒有實現協議的非可選
方法。可能會導致功能異常或觸發Crash
。
掃描規則
類
和分類
未實現NSObject
協議的非可選
方法
可選的修複方式
-
對應的類實現缺失的
非可選
協議方法 -
將對應的協議方法標識為
optional
可選方法
重覆的ObjC類
多個動態庫
和靜態庫
之間存在同樣的類
。不會導致編譯失敗,但是運行時只會使用其中一個類,可能會導致功能異常或觸發Crash
。同時會增加包體積
。
掃描規則
- 多個
動態庫
和靜態庫
之間存在同樣的NSObject類
符號
可能的修複方式
- 移除重覆的類
性能
使用動態庫
使用動態庫會增加啟動
耗時。
掃描規則
Macho
為動態庫
可選的修複方式
-
使用
靜態庫
-
使用
Mergeable Library
實現+load
方法的類
APP啟動
後會執行所有+load
方法,減少+load
方法可以降低啟動耗時。
掃描規則
- 實現
+load
方法的NSObject
類
可選的修複方式
-
移除
+load
方法 -
使用
+initialize
替代
自定義配置
重要配置
systemFrameworkPaths
可以基於自身項目進行系統庫目錄的配置,解析工程時也會對系統庫進行解析。配置系統庫目錄對於未使用方法的查找可以提供更多的信息避免誤報。但是配置更多會導致執行的更慢,建議至少配置Foundation
/UIKit
。
unusedObjCProperty-enable
unusedObjCProperty
規則預設不開啟。
- 開啟未使用屬性檢查以後,會掃描
macho
的__TEXT
段,會增加分析的耗時。
unusedClass-swiftEnable
unusedClass-swiftEnable
預設不開啟。
-
開啟
Swift
類檢查以後,會掃描macho
的__TEXT
段,會增加分析的耗時。 -
未使用
Swift
類的項目建議不要開啟,如果考慮執行性能的話Swift
使用相對比較多的再開啟。
提示:掃描
macho
的__TEXT
段需要使用XCode
Run編譯出的包,不能直接使用用於上架APP Store
構建出的包。主要是Debug
會包含更多的信息用於掃描。
配置屬性
/Users/Test/APPAnalyzeCommand -ipa /Users/Desktop/ipas/APPMobile/APPMobile.app -config /Users/Desktop/ipas/config.json --output /Users/Desktop/ipas/APPMobile
可基於自身項目需要,添加下列規則可配置參數。在使用APPAnalyzeCommand
指令時添加--config
配置文件地址。
{
"systemFrameworkPaths": ["/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore", "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation"
], // 配置系統庫。會極大增加未使用方法的誤報
"rules": {
"dynamicCallObjCClass": { // 動態調`ObjC類
"enable": false, // 是否啟用
"excludeClasslist": [ // 過濾類名
"NSObject",
"param"
]
},
"incorrectObjCPropertyDefine": { // 錯誤的 ObjC 屬性定義
"enable": false // 是否啟動
},
"largeResource": { // 大資源
"maxSize": 20480 // 配置大資源判定大小。預設 20480Byte=20KB
},
"unusedObjCProperty": { // 未使用的 ObjC 屬性
"enable": false, // 是否啟用。預設不開啟
"excludeTypes": ["NSString", "NSArray", "NSDictionary", "NSNumber", "NSMutableArray", "NSMutableDictionary", "NSSet"] // 過濾掉部分類型的屬性
},
"unusedClass": { // 未使用的類
"swiftEnable": false, // 是否支持 Swift 類。預設不支持
"excludeSuperClasslist": ["JDProtocolHandler", "JDProtocolScheme"],// 如果類繼承了某些類就過濾
"excludeProtocols": ["RCTBridgeModule"], // 如果類實現了某些協議就過濾
"excludeClassRegex": ["^jd.*Module$", "^PodsDummy_", "^pg.*Module$", "^SF.*Module$"] // 過濾掉名字元合正則表達式的類
},
"unusedObjCMethod": { // 未使用的 ObjC 方法
"excludeInstanceMethods": [""], // 過濾掉某些名字的對象方法
"excludeClassMethods": [""], // 過濾掉某些名字的類方法
"excludeInstanceMethodRegex": ["^jumpHandle_"], // 過濾掉名字元合正則表達式的對象方法
"excludeClassMethodRegex": ["^routerHandle_"], // 過濾掉名字元合正則表達式的類方法
"excludeProtocols": ["RCTBridgeModule"] // 如果類集成了某些協議就不再檢查,例如 RN 方法
},
"loadObjCClass": { // 調用 ObjC + load 方法
"excludeSuperClasslist": ["ProtocolHandler"], // 如果類繼承了某些類就過濾
"excludeProtocols": ["RCTBridgeModule"] // 如果類實現了某些協議就過濾,例如 RN 方法
},
"unusedImageset": { // 未使用 imageset
"excludeNameRegex": [""] // 過濾掉名字元合正則表達式的imageset
},
"unusedResource": { // 未使用資源
"excludeNameRegex": [""] // 過濾掉名字元合正則表達式的資源
}
}
}
組件化工程掃描
可以基於APPAnalyzeCore.framework
定製實現自己的組件化工程掃描,或者添加基於自身組件化工程的檢查規則。詳情可以看Demo
。
基於組件化掃描方式有以下優勢:
-
細化數據粒度
- 可以細化每個模塊的包體積和包體積問題,更容易進行包體積優化。 -
更多的檢查
- 例如檢查不同組件同一個Bundle
包含同名的文件,不同組件包含同一個category
方法的的實現。 -
檢查結果更準確
- 例如ObjC
未使用方法的檢查,只要存在一個和方法名同樣的調用就表示方法有被使用到。但是整個ipa
中可能存在很多一樣的方法名但是只有一個方法有真正被調用到,如果細分到組件的粒度就可以發現更多問題。
提示:只有APP主工程無代碼,全部通過子組件以
framework
的形式導入二進位庫的方式的工程才適合這種模式。
其他
掃描質量如何
這套工具我們團隊內部開發加逐步完善有一年的時間了。基於此工具修改了幾十個組件的包體積問題,同時不斷的修複誤報問題。目前現有提供的這些規則檢查誤報率是很低的,只有極少數幾個規則可能存在誤報的可能性,總體掃描質量還是很高的。
和社區開源的工具有什麼差異
我們在早期調研了社區的幾個同類型的開源工具,主要存在以下幾個問題:
-
擴展性不夠
- 無法支持項目更好的擴展定製能力,例如添加掃描規則、支持不同類型掃描方式、生成更多的報告類型。 -
功能不全
- 只提供部分能力,例如只提供未使用資源
或者未使用類
。 -
無法生成包體積數據
- 無法生成包體積完整的數據。 -
檢查質量不高
- 掃描發現的錯誤數據多,或者有一些問題不能被髮現。
開源計劃
後續一定會開源。主要是希望調整一些內部結構再開源,開源後就不方便調整。順便修複一些常見的問題。
開源帶來的好處
開源帶來的好處是,部分工程可以基於自身的業務需要,擴展定製自己的掃描工具。同時也可以將一些更好的想法實現添加進來。
-
擴展解析方式
- 目前只支持ipa
模式掃描,很快會開放支持project
組件化工程的掃描方式。基於組件化工程
的掃描可以更加準確,但是不同的公司組件化工程
的構建方式可能是不一樣的,有需要可以在上層定製自身組件化工程
的掃描解析。 -
擴展掃描規則
- 雖然現在已經添加了比較多的通用性的規則,同時提供了一定的靈活性配置能力。但是不同的項目可能需要定製一些其他的規則,這些規則沒辦法通過在現有規則上添加配置能力實現。 -
擴展數據生成
- 預設包里只包含兩種數據生成,包體積
數據還有包體積待修複問題
數據。可以擴展更多的數據生成格式,例如我們自身的項目就有添加基於組件的依賴樹格式。
後續規劃
組件化工程支持
添加更多用於組件化工程的掃描
對於 Swift 更好的支持
對於Swift
語言只要開啟XCode
編譯優化以後就能在生成產物的時候支持無用代碼的移除,包括未使用類型
和未使用方法
的自動移除,但是依然有部分場景不會進行優化。所以這一塊也是後續完善的重點:
-
未使用屬性
- 編譯器不會對於未使用屬性
進行移除,包括class
和struct
的屬性。 -
未使用方法
- 對於class
的方法,編譯器並不會進行移除,即使沒有申明[@objc](https://my.oschina.net/TnhqVdFXL8vnu)
進行消息派發。
相關鏈接
作者:京東零售 何驍
來源:京東雲開發者社區 轉載請註明來源