自动发现问题
-
提升效率 - 人工排查问题效率低,对于常见的问题尽可能自动扫描出来。并且对于组件化工程来讲,很多外部组件是通过Framework方式提供,没有仓库源码权限用于分析包体积问题。 -
流程化 - 形成自动化的质量流程,添加到CI流水线自动发现包体积问题。
数据指标量化
-
包体积问题 - 提供数据化平台查看每个组件的包体积待修复问题 -
包体积大小 - 提供数据化平台查看每个组件的包体积占比,包括总大小,单个文件二进制大小和每个资源大小。可以针对不同的APP版本进行组件化粒度的包体积数据对比,更方便查看每个版本的组件大小增量。
提示:基于组件化工程的扫描方式内部支持,只是暂时不对外开放。
安装
使用
$ /Users/Test/APPAnalyzeCommand --help
OPTIONS:
--version <version> 当前版本 1.0.0
--output <output> 输出文件目录。必传参数
--config <config> 配置JSON文件地址。非必传参数
--ipa <ipa> ipa.app文件地址。必传参数
-h, --help Show help information.
执行
提示:不能直接使用AppStore的包,AppStore的包需要砸壳。建议尽量使用XCodeDebug的包。
/Users/Test/APPAnalyzeCommand --ipa ipas/JDAPP/JDAPP.app --output ipas/JDAP
提示:如果提示permission denied没有权限,执行sudo chmod -R 777 /Users/a/Desktop/ipas/APPAnalyzeCommand即可。
生成产物
包体积信息
-
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类被引用 -
没有被当做父类使用 -
没有使用的字符串和类名一致 -
没有被当做属性类型使用 -
没有被创建或调用方法 -
没有实现+load方法
可选的修复方式
-
移除未使用的类 -
Swift类如果只是用了static方法考虑修改成Enum类型 -
如果只是在类型转换时使用了也会检测出是未使用的类,例如(ABCClass *)object;。建议检查是否真的有没有到相关类后删除 -
对于ObjC,如果只是作为方法参数类型使用也会被检测出是未使用的类。建议删除相关方法即可。
提示:删除类相对是一种安全的行为,因为删除后如果有被使用到会产生编译时错误。虽然有做字符串调用的扫描过滤,不过还是建议检查是否可能被Runtime动态创建调用
未使用的ObjC协议
扫描规则
-
对应的协议没有被类引用
可选的修复方式
-
移除未使用的协议
Bundle内多Scale图片
扫描规则
-
同一个Bundle内存在同名但是scale不同的图片。例如[email protected]/[email protected]
可选的修复方式
-
移除Scale更低的图片
大资源
扫描规则
-
某个文件超过设置的大资源限额
可选的修复方式
-
移除资源动态下发 -
使用更小的数据格式,例如使用更小的图片格式
重复的资源文件
扫描规则
-
多个文件MD5一致即判定为重复文件。
可选的修复方式
-
移除多余的文件
未使用的类Property属性
扫描规则
-
对应的属性没有被调用 set/get 方法,同时也没有被_的方式使用 -
不是来自实现协议的属性 -
不是来自Category的属性 -
不存在字符串使用和属性名一致
可选的修复方式
-
移除对应的属性 -
如果是接口协议的属性,需要添加类实现此接口
注意事项
-
可能存在部分动态使用的场景,需要进行一定的检查。例如一些继承NSObject的数据模型类,可能存在属性没有被直接使用到,但是可能会被传唤成JSON作为参数的情况。例如后台下发的数据模型
未使用的ImageSet/DataSet
扫描规则
-
未检测到和Imageset同样名字的字符串使用
可选的修复方式
-
移除ImageSet/DataSet
注意事项
-
某些Swift代码中使用的字符串不能被发现所以会被当做未使用。 -
使用字符串拼接的名字作为imageset的名字。 -
被合成到PackedAssetImage里的Imageset不能被扫描出来
未使用的ObjC方法
扫描规则
-
不存在和此方法一样的方法名使用 -
不存在使用的字符串和方法名一致 -
不是来自父类或Category的方法 -
不是来自实现接口的方法 -
不是属性 set/get 方法
可选的修复方式
-
移除对应方法
未使用的分类方法
扫描规则
-
不存在和此方法一样的方法名使用 -
不存在和方法名一致的字符串使用 -
不是来自父类或Category的方法 -
不是来自实现接口的方法
可选的修复方式
-
移除未使用的方法 -
如果是接口协议的方法,需要添加类实现此接口
未使用的资源文件
扫描规则
-
未检测到和文件名同样名字的字符串使用
可选的修复方式
-
移除资源
注意事项
-
某些Swift代码中使用的字符串不能被发现所以会被当做未使用 -
使用字符串拼接的名字作为资源的名字
安全
动态反射调用ObjC类
扫描规则
-
存在使用的字符串和NSObject子类类名相同
可选的修复方式
-
使用NSStringFromClass()获取类名字符串 -
使用Framework外部的类应该使用方法封装,除了少部分功能不应该使用反射去调用类
提示:包含继承NSObject的 swift 类。
ObjC属性内存申明错误
扫描规则
-
NSArray/NSSet/NSDictionary类型的属性使用strong申明 -
NSMutableArray/NSMutableSet/NSMutableDictionary类型的属性使用copy申明
可选的修复方式
-
修改strong/copy申明
冲突的分类方法
扫描规则
-
NSObject类的多个Category分类中存在多个相同的方法
修复方式
-
移除多余的分类方法
重复的分类方法
扫描规则
-
NSObject原始类和类的Category分类中有相同的方法
修复方式
-
移除重复的分类方法
未实现的ObjC协议方法
扫描规则
-
类和分类未实现NSObject协议的非可选方法
可选的修复方式
-
对应的类实现缺失的非可选协议方法 -
将对应的协议方法标识为optional可选方法
重复的ObjC类
扫描规则
-
多个动态库和静态库之间存在同样的NSObject类符号
可能的修复方式
-
移除重复的类
性能
使用动态库
扫描规则
-
Macho为动态库
可选的修复方式
-
使用静态库 -
使用Mergeable Library
实现+load方法的类
扫描规则
-
实现+load方法的NSObject类
可选的修复方式
-
移除+load方法 -
使用+initialize替代
自定义配置
重要配置
systemFrameworkPaths
unusedObjCProperty-enable
-
开启未使用属性检查以后,会扫描macho的__TEXT段,会增加分析的耗时。
unusedClass-swiftEnable
-
开启Swift类检查以后,会扫描macho的__TEXT段,会增加分析的耗时。 -
未使用Swift类的项目建议不要开启,如果考虑执行性能的话Swift使用相对比较多的再开启。
提示:扫描macho的__TEXT段需要使用XCodeRun编译出的包,不能直接使用用于上架APP Store构建出的包。主要是Debug会包含更多的信息用于扫描。
配置属性
/Users/Test/APPAnalyzeCommand -ipa /Users/Desktop/ipas/APPMobile/APPMobile.app -config /Users/Desktop/ipas/config.json --output /Users/Desktop/ipas/APPMobile
{
"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": [""] // 过滤掉名字符合正则表达式的资源
}
}
}
组件化工程扫描
-
细化数据粒度 - 可以细化每个模块的包体积和包体积问题,更容易进行包体积优化。 -
更多的检查 - 例如检查不同组件同一个Bundle包含同名的文件,不同组件包含同一个category方法的的实现。 -
检查结果更准确 - 例如ObjC未使用方法的检查,只要存在一个和方法名同样的调用就表示方法有被使用到。但是整个ipa中可能存在很多一样的方法名但是只有一个方法有真正被调用到,如果细分到组件的粒度就可以发现更多问题。
提示:只有APP主工程无代码,全部通过子组件以framework的形式导入二进制库的方式的工程才适合这种模式。
扫描质量如何
和社区开源的工具有什么差异
-
扩展性不够 - 无法支持项目更好的扩展定制能力,例如添加扫描规则、支持不同类型扫描方式、生成更多的报告类型。 -
功能不全 - 只提供部分能力,例如只提供未使用资源或者未使用类。 -
无法生成包体积数据 - 无法生成包体积完整的数据。 -
检查质量不高 - 扫描发现的错误数据多,或者有一些问题不能被发现。
开源计划
开源带来的好处
-
扩展解析方式 - 目前只支持ipa模式扫描,很快会开放支持project组件化工程的扫描方式。基于组件化工程的扫描可以更加准确,但是不同的公司组件化工程的构建方式可能是不一样的,有需要可以在上层定制自身组件化工程的扫描解析。 -
扩展扫描规则 - 虽然现在已经添加了比较多的通用性的规则,同时提供了一定的灵活性配置能力。但是不同的项目可能需要定制一些其他的规则,这些规则没办法通过在现有规则上添加配置能力实现。 -
扩展数据生成 - 默认包里只包含两种数据生成,包体积数据还有包体积待修复问题数据。可以扩展更多的数据生成格式,例如我们自身的项目就有添加基于组件的依赖树格式。
后续规划
组件化工程支持
对于 Swift 更好的支持
-
未使用属性 - 编译器不会对于未使用属性进行移除,包括class和struct的属性。 -
未使用方法 - 对于class的方法,编译器并不会进行移除,即使没有申明@objc进行消息派发。
相关链接
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
{{o.name}}
{{m.name}}