分享一款用於分析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
協定沒有被類使用
掃描規則
可選的修復方式
Bundle
內同一張圖片包含多個Scale
會導致更大的包體積。
掃描規則
Bundle
記憶體在同名但是scale
不同的圖片。例如[email protected]
/[email protected]
可選的修復方式
Scale
更低的圖片檔案大小超過一定大小的即為大資源,預設為20KB
。
掃描規則
可選的修復方式
移除資源動態下發
使用更小的資料格式,例如使用更小的圖片格式
存在多個同樣的重複檔案。
MD5
一致即判定為重複檔案。可選的修復方式
ObjC
類中定義的屬性沒有被使用到。
掃描規則
對應的屬性沒有被呼叫 set/get 方法,同時也沒有被_
的方式使用
不是來自實現協定的屬性
不是來自Category
的屬性
不存在字串使用和屬性名一致
可選的修復方式
移除對應的屬性
如果是介面協定的屬性,需要新增類實現此介面
注意事項
NSObject
的資料模型類,可能存在屬性沒有被直接使用到,但是可能會被傳喚成JSON
作為引數的情況。例如後臺下發的資料模型包含的Imageset
/DataSet
並沒有被使用到。
掃描規則
Imageset
同樣名字的字串使用可選的修復方式
注意事項
某些Swift
程式碼中使用的字串不能被發現所以會被當做未使用。
使用字串拼接的名字作為imageset的名字。
被合成到PackedAssetImage
裡的Imageset
不能被掃描出來
定義的ObjC
Category 方法並未被使用到。
掃描規則
不存在和此方法一樣的方法名使用
不存在使用的字串
和方法名一致
不是來自父類別或Category
的方法
不是來自實現介面的方法
不是屬性 set/get 方法
可選的修復方式
定義的ObjC
Category 方法並未被使用到。
掃描規則
不存在和此方法一樣的方法名使用
不存在和方法名一致的字串
使用
不是來自父類別或Category
的方法
不是來自實現介面的方法
可選的修復方式
移除未使用的方法
如果是介面協定的方法,需要新增類實現此介面
包含的檔案資源並沒有被使用到。這裡的資源不包含Imageset
/DataSet
。
掃描規則
可選的修復方式
注意事項
某些Swift
程式碼中使用的字串不能被發現所以會被當做未使用
使用字串拼接的名字作為資源的名字
存在類名和字串一致,可能使用NSClassFromString()
方法動態呼叫類。當字串
或類名變更時無法利用編譯時檢查發現問題,可能會導致功能異常。
掃描規則
字串
和NSObject子類
類名相同可選的修復方式
使用NSStringFromClass()
獲取類名字串
使用Framework
外部的類應該使用方法封裝,除了少部分功能不應該使用反射去呼叫類
提示:包含繼承
NSObject
的 swift 類。
一些特殊的NSObject
型別的屬性記憶體型別申明錯誤,可能會導致功能異常或觸發Crash
。
掃描規則
NSArray
/NSSet
/NSDictionary
型別的屬性使用strong
申明
NSMutableArray
/NSMutableSet
/NSMutableDictionary
型別的屬性使用copy
申明
可選的修復方式
strong
/copy
申明ObjC
同一個類的多個Category
分類中存在多個相同的方法,由於執行時最終會載入方法可能是不確定的,可能會導致功能異常等未知的行為。
掃描規則
NSObject類
的多個Category
分類中存在多個相同的方法修復方式
ObjC
原始類和類的Category
分類中有相同的方法,分類中的方法會覆蓋原始類的方法,可能會導致功能異常等未知的行為。
掃描規則
NSObject
原始類和類的Category
分類中有相同的方法修復方式
類實現了某個ObjC
協定,但是沒有實現協定的非可選
方法。可能會導致功能異常或觸發Crash
。
掃描規則
類
和分類
未實現NSObject
協定的非可選
方法可選的修復方式
對應的類實現缺失的非可選
協定方法
將對應的協定方法標識為optional
可選方法
多個動態庫
和靜態庫
之間存在同樣的類
。不會導致編譯失敗,但是執行時只會使用其中一個類,可能會導致功能異常或觸發Crash
。同時會增加包體積
。
掃描規則
動態庫
和靜態庫
之間存在同樣的NSObject類
符號可能的修復方式
使用動態庫會增加啟動
耗時。
掃描規則
Macho
為動態庫可選的修復方式
使用靜態庫
使用Mergeable Library
+load
方法的類APP啟動
後會執行所有+load
方法,減少+load
方法可以降低啟動耗時。
掃描規則
+load
方法的NSObject
類可選的修復方式
移除+load
方法
使用+initialize
替代
可以基於自身專案進行系統庫目錄的設定,解析工程時也會對系統庫進行解析。設定系統庫目錄對於未使用方法的查詢可以提供更多的資訊避免誤報。但是設定更多會導致執行的更慢,建議至少設定Foundation
/UIKit
。
unusedObjCProperty
規則預設不開啟。
macho
的__TEXT
段,會增加分析的耗時。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
語言只要開啟XCode
編譯優化以後就能在生成產物的時候支援無用程式碼的移除,包括未使用型別
和未使用方法
的自動移除,但是依然有部分場景不會進行優化。所以這一塊也是後續完善的重點:
未使用屬性
- 編譯器不會對於未使用屬性
進行移除,包括class
和struct
的屬性。
未使用方法
- 對於class
的方法,編譯器並不會進行移除,即使沒有申明[@objc](https://my.oschina.net/TnhqVdFXL8vnu)
進行訊息派發。
作者:京東零售 何驍
來源:京東雲開發者社群 轉載請註明來源