介绍

分享一款用于剖析iOSipa包的脚本东西,运用此东西能够主动扫描发现可修正的包体积问题,一起能够生成包体积数据用于查看。这块东西咱们团队内部现已运用很长一段时刻,希望能够协助到更多的开发同学更加效率的优化包体积问题。

东西下载地址

背景

APPAnalyze东西最早诞生主要是为了处理以下包体积管理的问题:

关于定位下沉市场的APP来讲,包体积是一个非常重要的功能目标,包体积过大会影响用户下载APP的志愿。可是在早期咱们缺少一些手段协助咱们更高效的去进行包体积管理。

主动发现问题

  • 提升效率 – 人工排查问题效率低,关于常见的问题尽或许主动扫描出来。而且关于组件化工程来讲,许多外部组件是经过Framework办法供给,没有仓库源码权限用于剖析包体积问题。
  • 流程化 – 形成主动化的质量流程,增加到CI流水线主动发现包体积问题。

数据目标量化

  • 包体积问题 – 供给数据化平台查看每个组件的包体积待修正问题
  • 包体积巨细 – 供给数据化平台查看每个组件的包体积占比,包含总巨细,单个文件二进制巨细和每个资源巨细。能够针对不同的APP版别进行组件化粒度的包体积数据对比,更方便查看每个版别的组件巨细增量。

完结办法

咱们选择了不依靠源码而是直接扫描二进制库的办法来完结这个才能,整体的履行流程一下:

京东京喜 iOS 包体积分析工具

提示:根据组件化工程的扫描办法内部支撑,仅仅暂时不对外开放。

运用指南

装置

无需装置。经过下载链接直接下载终端可履行命令文件APPAnalyzeCommand到本地即可运用。

APPAnalyzeCommand 下载地址

运用

$ 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的包。

APPAnalyzeCommand --ipa ipas/JDAPP/JDAPP.app --output ipas/JDAPP

提示:假如提示permission denied没有权限,履行sudo chmod -R 777 /Users/a/Desktop/ipas/APPAnalyzeCommand即可。双击APPAnalyzeCommand是否能够直接引发终端程序。

生成产品

京东京喜 iOS 包体积分析工具
指令履行完结今后,会在ouput参数指定的文件夹生成APPAnalyze文件夹。具体文件介绍如下:

包体积信息

  • app_size.html – 展现ipa每个framework的包体积数据,可直接用浏览器翻开。

提示:依照主程序和动态库进行粒度区分

京东京喜 iOS 包体积分析工具

  • framework_size.html – 展现单个framework一切的包体积数据,二级页面不要直接翻开

京东京喜 iOS 包体积分析工具

提示:XCode生成Assets.car时会将一些小图片拼接成一张PackedAssetImage的大图片。

  • package_size.jsonipa包体积 JSON 数据

包体积待修正问题

  • app_issues.html – 展现ipa每个framework的包体积待修正问题数量,可直接用浏览器翻开。

提示:依照主程序和动态库进行粒度区分

京东京喜 iOS 包体积分析工具

  • framework_issues.html – 展现单个framework一切的待修正问题详细数据,二级页面不要独自翻开

京东京喜 iOS 包体积分析工具

  • issues.jsonipa待修正包体积问题 JSON 数据

提示:json数据可用于搭建自己的数据平台,扩展更多的才能。例如查看不同APP版别以及支撑多个APP版别对比等。

规矩介绍

包体积

未运用的类

界说了类没有被运用到,包含ObjC类和Swift类。

扫描规矩
  • 没有查到到对应的ObjC类被引用
  • 没有被作为父类运用
  • 没有运用的字符串和类名共同
  • 没有被作为特点类型运用
  • 没有被创立或调用办法
  • 没有完结+load办法
可选的修正办法
  • 移除未运用的类
  • Swift类假如仅仅用了static办法考虑修改成Enum类型
  • 假如仅仅在类型转换时运用了也会检测出是未运用的类,例如(ABCClass *)object;。主张查看是否真的有没有到相关类后删去
  • 关于ObjC,假如仅仅作为办法参数类型运用也会被检测出是未运用的类。主张删去相关办法即可。

提示:删去类相对是一种安全的行为,因为删去后假如有被运用到会产生编译时过错。虽然有做字符串调用的扫描过滤,不过仍是主张查看是否或许被Runtime动态创立调用

未运用的ObjC协议

界说了ObjC协议没有被类运用

扫描规矩
  • 对应的协议没有被类引用
可选的修正办法
  • 移除未运用的协议

Bundle内多Scale图片

Bundle内同一张图片包含多个Scale会导致更大的包体积。

扫描规矩
  • 同一个Bundle内存在同名可是scale不同的图片。例如a@2x.png/a@3x.png
可选的修正办法
  • 移除Scale更低的图片

大资源

文件巨细超越必定巨细的即为大资源,默许为20KB

扫描规矩
  • 某个文件超越设置的大资源限额
可选的修正办法
  • 移除资源动态下发
  • 运用更小的数据格局,例如运用更小的图片格局

重复的资源文件

存在多个相同的重复文件。

扫描规矩

  • 多个文件MD5共同即断定为重复文件。
可选的修正办法
  • 移除剩余的文件

未运用的类Property特点

ObjC类中界说的特点没有被运用到。

扫描规矩
  • 对应的特点没有被调用 set/get 办法,一起也没有被_的办法运用
  • 不是来自完结协议的特点
  • 不是来自Category的特点
  • 不存在字符串运用和特点名共同
可选的修正办法
  • 移除对应的特点
  • 假如是接口协议的特点,需求增加类完结此接口
注意事项
  • 或许存在部分动态运用的场景,需求进行必定的查看。例如一些承继NSObject的数据模型类,或许存在特点没有被直接运用到,可是或许会被传唤成JSON作为参数的状况。例如后台下发的数据模型

未运用的ImageSet/DataSet

包含的Imageset/DataSet并没有被运用到。

扫描规矩
  • 未检测到和Imageset相同姓名的字符串运用
可选的修正办法
  • 移除ImageSet/DataSet
注意事项
  • 某些Swift代码中运用的字符串不能被发现所以会被作为未运用。
  • 运用字符串拼接的姓名作为imageset的姓名。
  • 被合成到PackedAssetImage里的Imageset不能被扫描出来

未运用的ObjC办法

界说的ObjCCategory 办法并未被运用到。

扫描规矩
  • 不存在和此办法相同的办法名运用
  • 不存在运用的字符串和办法名共同
  • 不是来自父类或Category的办法
  • 不是来自完结接口的办法
  • 不是特点 set/get 办法
可选的修正办法
  • 移除对应办法

未运用的分类办法

界说的ObjCCategory 办法并未被运用到。

扫描规矩
  • 不存在和此办法相同的办法名运用
  • 不存在和办法名共同的字符串运用
  • 不是来自父类或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段需求运用XCodeRun编译出的包,不能直接运用用于上架APP Store构建出的包。主要是Debug会包含更多的信息用于扫描。

装备特点

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": [""] // 过滤掉姓名契合正则表达式的资源
        }
    }
}

其他

扫描质量怎么

这套东西咱们团队内部开发加逐步完善有一年的时刻了。根据此东西修改了几十个组件的包体积问题,一起不断的修正误报问题。现在现有供给的这些规矩查看误报率是很低的,只要极少数几个规矩或许存在误报的或许性,整体扫描质量仍是很高的。

和社区开源的东西有什么差异

咱们在早期调研了社区的几个同类型的开源东西,主要存在以下几个问题:

  • 扩展性不行 – 无法支撑项目更好的扩展定制才能,例如增加扫描规矩。
  • 功用不全 – 只供给部分才能,例如只供给未运用资源或许未运用类
  • 无法生成包体积数据 – 无法生成包体积完整的数据。
  • 查看质量不高 – 扫描发现的过错数据多,或许有一些问题不能被发现。

开源方案

后续必定会开源。最近刚做完必定的代码重构,然后准备申请公司的开源流程。

开源带来的优点

开源带来的优点是,部分工程能够根据本身的业务需求,扩展定制自己的扫描东西。一起也能够将一些更好的主意完结增加进来。

  • 扩展解析办法 – 现在只支撑ipa模式扫描,很快会开放支撑project组件化工程的扫描办法。根据组件化工程的扫描能够更加准确,可是不同的公司组件化工程的构建办法或许是不相同的,有需求能够在上层定制本身组件化工程的扫描解析。
  • 扩展扫描规矩 – 虽然现在现已增加了比较多的通用性的规矩,一起供给了必定的灵活性装备才能。可是不同的项目或许需求定制一些其他的规矩,这些规矩没办法经过在现有规矩上增加装备才能完结。
  • 扩展数据生成 – 默许包里只包含两种数据生成,包体积数据还有包体积待修正问题数据。能够扩展更多的数据生成格局,例如咱们本身的项目就有增加根据组件的依靠树格局。

后续规划

组件化工程扫描

近期方案优先支撑根据组件化工程的扫描办法。方案供给一种通用的才能,能够经过装备包含一切组件信息(二进制/资源/依靠联系)的这样一个json文件来装备组件工程。 不过开源后也能够根据本身的需求来定制解析办法。

根据组件化扫描办法有以下优势:

  • 细化数据粒度 – 能够细化每个模块的包体积和包体积问题,更容易进行包体积优化。
  • 更多的查看 – 例如查看不同组件同一个Bundle包含同名的文件,不同组件包含同一个category办法的的完结。
  • 查看结果更准确 – 例如ObjC未运用办法的查看,只要存在一个和办法名相同的调用就表明办法有被运用到。可是整个ipa中或许存在许多相同的办法名可是只要一个办法有真正被调用到,假如细分到组件的粒度就能够发现更多问题。

提示:只要APP主工程无代码,全部经过子组件以framework的形式导入二进制库的办法的工程才合适这种模式。

关于 Swift 更好的支撑

关于Swift言语只要敞开XCode编译优化今后就能在生成产品的时候支撑无用代码的移除,包含未运用类型未运用办法的主动移除,可是仍然有部分场景不会进行优化。所以这一块也是后续完善的重点:

  • 未运用类 – 编译器不会关于未运用class进行移除,即使是承继NSObject的子类。
  • 未运用特点 – 编译器不会关于未运用特点进行移除,包含classstruct的特点。
  • 未运用办法 – 关于class的办法,编译器并不会进行移除,即使没有声明@objc进行音讯派发。

相关链接

  • Github地址