前言
随着APP的业务迭代,需求累积的越来越多,APP内引入的库或是各种业务功能代码也累积的越来越多,APP的包体不可避免的增大。包体太大对于APP的推广,用户的下载使用意愿都会有一定的影响,所以说包体瘦身是项目中无可避免的一项性能优化。
首要工作:建立监管体系
包体瘦身,并不是一个单纯的技术项,或者说并不是一个单次的性能优化需求,包体瘦身应该是一个长期的固定的工作内容,在进行实际的瘦身前,首先应该实现一个对当前APP大小的完善的监控体系,可以按照各个业务模块、资源等方式,把整体的大小详细的记录下来,并且应该有一个长期的监管体系。
实现监管只要依赖对打包时生成的link map 文件。link map 是编译链接时可以生成的一个txt文件,它生成的目的就是帮助程序员分析包体大小。link map 记录了每个方法在当前的二进制架构下占据的空间。通过分析link map,我们可以了解每个类甚至每个方法占据了多少安装包空间。此外,link map 还可以分析引进的第三方库的代码空间占比,作为一个性能考察点。
Link map File
: 我们编写的源码需要经过编译、链接,最终生成一个可执行文件。在编译阶段,每个类会生成对应的.o文件(目标文件)。在链接阶段,会把.o文件和动态库链接在一起。Link Map File
就是这样一个记录链接相关信息的纯文本文件,里面记录了可执行文件的路径、CPU架构、目标文件、符号等信息。在Symbols部分,我们可以把类编号相同的size加起来,算出每个类或库占用的大小。在Object files部分根据类的编号可以查出对应的类。分析的结果对App安装包瘦身会有一些帮助。github上有很多实现了这样功能的开源工具,实现原理很简单,如有需要可自行查找。
监管体系的目标是把包体大小可视化,把各个业务占用的大小和占比用图标的方式展示出来,并且每次业务库发版的时候都加上代码增量的提醒,主工程集成打包的时候同样提示整个APP的代码增量提醒。此外,不断的查看比对工程构建出来的ipa包,也是一个重要且有必要的监控步骤,通过iOS_Images_Extractor
来查看Assets.car
,通过MachOView
可以查看编译后的mach-o
可执行文件。
包体优化: 瘦身方案
当有了监控体系后,我们对自己APP的大小就有大致概况的了解。此时就进入到瘦身这一步了。这一步网上的各个资料相当的多,这里总结一些常用的方案。
总的来说,优化的方向主要有四个,资源的优化,Xcode配置项,Mach-o文件的优化以及业务层面的原生功能下线
资源优化
主要是图片资源和代码源文件。
图片资源主要优化方向是:
-
图片压缩
-
使用更小的图片格式。常见的各种小Icon,尤其需要多种颜色的图片采用 iconfont;webp也可以作为动图使用
-
对PNG无损压缩,对于包体优化机会是没意义的。因为Xcode在打包时会先把PNG图片解压成Bitmap,然后自行进行图片压缩处理,所以图片压缩 只能采用有损压缩的方式,对图片的源文件进行处理。例如头条分享的方式:RGB with palette
-
-
图片云端下载
- On Demand Resource
-
无用图片删除,重复图片筛选
-
Pods资源管理
-
业务Pods库中的资源不用
resources_bundle
的形式引入到主工程,而是直接用resources
方式集成,因为APPLE 在构建 Assets.car时有优化处理(比如:将若干张小图片自动合并为一张 Packed Image) -
错误的使用
cocoapods
会带来的图片重复合并问题,简单来说就是Pod库中的图片资源既被当做的了零散的PNG资源在主工程中,又被打进了Assets.car的压缩包中,这样同一份文件就被引入了两份。这个问题最彻底的解决方案是通过解压IPA以及包内的Assets.car,通过工具对得到的所有资源图做对比排查。
正确的podspec举例:s.resources = "Pods/Resources/xxx.xcassets"使用 https://github.com/devcxm/iOS-Images-Extractor 来解压.car 压缩包
-
无用代码清理
无用代码一般是指 无用类 和 无用方法。业务迭代过程中,难免会有业务下线的情况,虽然可以从业务层面即使下架,但是代码层面的查找更详细,精确。
MachO文件中的
__DATA
段中有objc_classrefs
和objc_selrefs
段,分别近似于“被使用的类的集合”和“被使用的方法的集合”,同时也含有所有的类和方法的集合,通过取差集的方式可以筛选出未被使用的类和方法。
重复代码
重复代码是指功能类似的甚至是直接COPY的代码,项目中的工具类往往是重灾区。重复代码最好使用第三方工具来排查,例如 PMD
Xcode 配置项
Xcode 有一些的配置项能够带来非常明显的收益,不过需要结合项目的实际情况,不能盲目使用。
-
LTO,即
Link Time Optimization
苹果在2016年的WWDC What’s new in LLVM中详细介绍了这一功能。
LTO 带来的优化有:
(1)将一些函数內联化
(2)去除了一些无用代码
(3)对程序有全局的优化作用LTO 的缺点:
会降低编译链接的速度,因此只建议在打正式包时开启;
开启了LTO之后,link map的可读性明显降低,多出了很多数字开头的“类”(LTO的全局优化导致的),导致我们还经常需要手动关闭LTO打包来阅读link map。 -
Compress PNG Files (COMPRESS_PNG_FILES)
-
Optimization (ASSETCATALOG_COMPILER_OPTIMIZATION) 设置为space
-
Optimization Level, Release 环境设置为: Fastest, Smallest[-Os],这个是Xcode默认的设置,这里可以设置为Oz(Smallest,Aggressive Size Optimizations[-Oz])
Oz 是 Xcode 11 新增的编译优化选项。WWDC 2019 《What's New in Clang and LLVM》[3] 中对 Oz 有过介绍。Oz 的核心原理是对重复的连续机器指令外联成函数进行复用,和“内联函数”的原理正好相反。因此,开启 Oz,能减小二进制的大小,但同时理论上会带来执行效率的额外消耗。对性能(CPU)敏感的代码使用需要评估。
苹果给的参考数据是 4.5% 的包体积收益。
-
EXPORTED_SYMBOLS_FILE.
Xcode Build Settings 中的
EXPORTED_SYMBOLS_FILE
配置,控制着 Mach-O 中__LINKEDIT
段中 Export Info 的信息。动态链接器 dyld 在做符号绑定时,会读取被绑定的动态库或可执行文件的 Export Info 信息,得到一个符号对应的实际调用地址。如果正在被绑定的符号,在目标动态库的 Export Info 中缺失,dyld 则会抛出异常,表现为 App 崩溃。 -
Build Settings中去掉异常支持,Enable C++ Exceptions和Enable Objective-C Exceptions设置为NO,Other C Flags添加-fno-exceptions
注意:Enable C++ Excptions和Enable Objective-C Exceptions是指项目支持对错误的异常处理,比如try catch、throw之类的;所以如果项目中使用的有类似的异常处理的,这个关闭了之后会报错(Cannot use '@try' with Objective-C exceptions disabled)。包括宏定义中使用的有try{}、@finally{}之类的,比如@strongify等,如果关闭了最后打包的时候也会报错。
-fno-exceptions的意思是禁用异常机制,参考gcc,同样,当项目中有try thorw的时候,就不要设置这个选项为NO
-
Build Settings -> Architectures,Release下设置为arm64。去除32位。
-
Build Settings -> Generate Debug Symbols设置为NO。关闭生成调试符号,关闭后无法生成DSYM,不建议关闭。
MACH-O 处理
1、二进制段压缩
Mach-O 文件占据了 Install Size 中很大一部分比例,但并不是文件中的每个段/节在程序启动的第一时间都要被用到。可以在构建过程中将 Mach-O 文件中的这部分段/节压缩,然后只要在这些段被使用到之前将其解压到内存中,就能达到了减少包大小的效果,同时也能保证程序正常运行。由于苹果的一些限制,我们目前只压缩了
__TEXT,__gcc_except_tab
与__TEXT,__objc_methtype
两个节,然后在_dyld_register_func_for_add_image
的回调中对它进行解压。该方案累计优化了 3.5 MB Install Size。
2、__TEXT 段代码迁移
安装包经过压缩后的 Download Size 若超过 200 MB,在蜂窝网络下载 App 就会受到限制,这对新增会有较大影响。在 2020 年下半年,我们探索实践了 __TEXT 段迁移技术:在链接阶段使用
-rename_section
选项将__TEXT,__text
迁移到__BD_TEXT
,__text
,减少苹果对可执行文件的加密范围,提升可执行文件的压缩效率,从而减少 Download Size。使用该方案我们最终减少了 60 MB 的 Download Size 以及 2 MB 的 Install Size。详细的原理可以参考:《今日头条优化实践:iOS 包大小二进制优化,一行代码减少 60 MB 下载大小》
业务层面的原生功能下线
推进产品进行低收益需求下线
APP随着多个版本的迭代,可能有很多冗余需求,低收益甚至是零收益的需求隐藏在APP内,可以推动产品,也可以内部代码审查,去寻找代码量大但是可下线的需求。
动态化
把代码文件当做资源放到云端,在APP启动的合适时机去判断是否需要下载,可以使用一些动态化的语言来实现这个功能。不过有一些功能会要求在本地存放一些兜底文件,如果说兜底文件的大小本身过大的话,动态化在包体瘦身方面的作用就是负的了。是否要采用动态化实现某些功能,还是需要具体问题具体分析。
参考
https://www.infoq.cn/article/XUJL32hTDKYqAKz0hkMM
https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247484366&idx=1&sn=5a2d0e981c733e9eaec274b835600e67&chksm=e9d0c82cdea7413ada372bb936541c2a49de664b38daa6137c179ab6177231fa8b00ddf9acbc&scene=21#wechat_redirect
网友评论