包体积优化(理论篇)

1,009 阅读8分钟

为什么要进行包大小优化?

iOS开发过程中, 包体积优化是一个老生常谈的问题. 为什么要进行包大小优化? 在 iOS13以前, 苹果是不允许用户使用移动网络下载App, 虽然iOS13后取消了限制, 但是超过200M后, 会需要用户许可才会下载 (可以在设置中修改配置). 所以大于200M的App很容易在下载这一步就卡死用户.

我们通常使用上传ipa包后 Developer后台解析的大小作为标准

0e2e389bdb304221838f6fe9d1d03e84.png

如何优化?

一:图片/视频/文件等资源治理

图片/视频等资源一般是占用空间最大的, 无用或重复资源一般是我们治理的首选目标

  1. 移除无用/重复图片

    • Assets中删除1x图片; 对于现在的主流设备, 1x的图片已经完全没有必要了, 所以可以大胆删除
    • 关键字搜索, 手动删除; 针对一些特殊,频次低的图片可以这么做
    • LSUnusedResources; 可以快速查找项目中未使用的图片, 但查找到的图片需要二次确认, 防止误删一些字符串拼接使用的图片
    • fdupes; Linux下查找重复文件工具, 可以对比重复文件
  2. 图片/资源压缩

    • tinypng等压缩工具压缩图片

    • Build Setting选项 Compress PNG Files 可以自动压缩图片 (自己使用压缩工具压缩图片的话建议关闭, 另外对Assets中的图片无效)

    • Build Setting选项 Remove Text Medadata From PNG Files 可以移除图片中的'作者,位置'等元数据, 减少些许大小(积少成多)

    • 将图片放入Assets (包含pod库中的图片); Assets中的图片, 苹果会单独做压缩处理, 并且可以根据2x, 3x分别打包

    • js,html文件压缩, 视频文件压缩; 除了图片外, 其他资源都可以通过压缩来达到减少包体积的目的

    不同格式的图片, 体积大小也不一样; 比如webp会比jpg小40%左右, 选择合适格式的图片也是治理的一个思路

  3. 资源异步下载

    • 根据业务需求, 将一些资源放到服务器中, 在某个时刻再下载使用; 这样操作需要考虑网络异常时对业务的影响程度, 或增加保底机制

    • On-Demand Resources 这是苹果自身提供的类似的功能, 并且提供了多种模式配合多种场景使用

    如果从未进行过资源治理, 通过对图片等资源的压缩删减治理我们可以快速得到显著的效果; 但是随着版本的迭代, 又会有新的图片/资源添加到项目中. 所以资源治理是一个长期的防劣化的过程, 虽然后期的效果甚微, 但却是非做不可的事情

二:Framework 瘦身

  • 删除已经被废弃使用的指令集; 可以在 Build Setting -Excluded Architectures 中删除不支持的指令集, 如 armv7s, 现阶段几乎可以大胆的只使用 arm64;
  • 用动态库替代静态库; 用动态库会减小包体积,但会增加启动时间,增加了运行时载入的过程,并且依赖外部环境,需多方面考虑进行取舍
  • 慎重引入第三方库; 尽量选择体积小的第三方库, 或对源代码进行删减; 同时可以使用 lipo -thin 等相关指令删除三方库中不支持的指令集

三:代码瘦身及无用代码删减

  • LinkMap

LinkMap中Symbols会列出所有方法, 类及他们的大小; 通过对LinkMap文件的分析, 我们可以知道每个类占多大体积针对不同的类进行单独瘦身; 同时通过LinkMap可以获得所有类和方法的全集;
iSee 是一个可以分析可执行文件, 查询可执行文件大小的工具

  • Mach-O文件

通过otool或MachOView可以逆向Mach-O文件的内容, 其中__objc_selrefs 这个 section 来获取 selector 参数的,另外 __objc_classrefs 和 __objc_superrefs 这两个 section 可以获取调用过的类和父类;

__DATA -> __objc_classrefs 记录了所有引用类的地址, __DATA -> __objc_classlist记录了所有类的地址, 取得两者的差集, 就可以得到所有的无用类; 当然这个方法对OC代码特别有效, 而Swift代码需要一些更复杂的操作, 可以参考58同城方案检测

四:XCode编译选项对包体积的影响

  • Valid Architectures

    设置编译生成的 ipa 包所支持的架构,不支持32位以及 iOS8 ,可去掉 armv7及之前的架构 ,减小生成的 ipa 包。

  • Strip Link Product 和 Deployment Postprocessing

    Strip Linked Product 默认为 Yes,Deployment Postprocessing 默认为 No; Strip Linked Product 在 Deployment Postprocessing 设置为 YES 的时候才生效。当Strip Linked Product设为YES的时候,ipa会去除掉symbol符号断点,运行 App 断点不会中断,在程序中打印[NSThread callStackSymbols]也无法看到类名和方法名。而在程序崩溃时,终端的函数调用栈中也无法看到类名和方法名。但是不会影响正常的崩溃日志生成和解析,依然可以通过符号表来解析崩溃日志,适合线上使用,建议在 release 下都设置为 Yes。

  • Generate Debug Symbols

    默认为 Yes,当设置为 Yes 时,编译生成的 .o 文件会更大,包含了断点信息和符号化的调试信息,方便开发阶段调试,建议在 release 下设置为 No,此时线上需要获取崩溃信息时搭配编译生成的dSYM文件解析符号。

  • Enable C++ Exceptions 和 Enable Objective-C Exceptions

    默认都为 Yes,用于捕获 C++ 和 OC 的异常,如果项目中没有用 try-catch 可以在release下设置为 No,配合在 Other C Flags 添加 -fno-exceptions 和 -fno-rtt,会有比较明显的体积减小。

  • Link Time Optimization

    LTO能带来的优化有:(1)将一些函数內联化 (2)去除了一些无用代码 (3)对程序有全局的优化作用 但LTO也会带来一点副作用。LTO会降低编译链接的速度,因此只建议在打正式包时开启;开启了LTO之后,link map的可读性明显降低,多出了很多数字开头的“类”(LTO的全局优化导致的),导致我们还经常需要手动关闭LTO打包来阅读link map。

  • Make Strings Read-Only

    默认为 Yes,复用字符串字面量。

  • Dead Code Stripping

    默认为 Yes,去除冗余代码。

  • Optimization Level

    Release 下默认为 Fastest, Smalllest[-Os],自动优化代码。

  • Symbols Hidden by Default

    Release 下默认为 Yes,会移除符号信息,把所有符号都定义成 private extern。

  • Strip Swift Symbols

    默认为 Yes,移除 Swift 相关的符号表,运行时再从 SWIFT 标准库中获取符号,从而减少应用体积

五:App Thinning

App Thinning是App Store在安装App时进行的一系列优化, 尽可能的减少安装包的大小. 在上传 App 时,可以勾选 App Thinning开启设置。分为三个部分: Slicing、Bitcode、On-Demand Resources。

  • Slicing

    在向 iTunes connect 上传 App 后,会做 App 进行分割,创建不同的变体来适配不同的设备,不同变体之间的差异主要体现在系统指令集和资源文件上。 在项目的 xcassets 目录添加图片,2x 和 3x 图会分别放入到不同的变体中,用户从 App Store 下载时只下载特定的变体,而不会下载全部的指令集和资源文件。

  • Bitcode

    Bitcode 是编译好的程序中间码,使用 Bitcode 上传到 iTunes Connect 的 app 将会在 Apple 服务器中进行链接和编译。该步骤主要是方便 Apple 推出新的架构或LLVM优化时无需重新上传 App。

  • On-Demand Resources (官方好像已经遗忘了这个功能)

    如上文所讲, ODR是苹果提供的将资源后置下载的方式, 可以减少App的下载体积; 根据不同的需求, 可以分为不同的配置

    Initial install tags. 资源和App同时下载,资源已经包含在App包里; 会影响包体积

    Prefetch tag order. 在App安装后开始下载,按照预加载列表中的顺序依次下载; 不会影响包体积 <根据TestFilght测试, 即使选择这个选项, 也需要在代码中进行手动下载, 而并非官方文档中所说的App下载后自动下载资源>

    Dowloaded only on demand. 开发者手动请求时才开始下载; 不会影响包体积

总结

包体积优化是一场持久战; 优化完一次后, 随着业务的迭代, 肯定会有新的资源/代码产生, 所以我们要时刻保持资源优化;
在实际项目瘦身过程中,上面列举的方式不一定都适用, 不同的业务需求有不同的方案;
在优化过程中, 宁可多花时间, 也要避免误操作, 毕竟瘦身虽可贵,安全价更高...

参考资料

  1. LSUnusedResources: github.com/tinymind/LS…
  2. fdupes: www.51cto.com/article/706…
  3. On-Demand Resources: www.jianshu.com/p/87079de32…
  4. WBBlades: github.com/wuba/WBBlad…
  5. Mach-o文件分析多余的类和方法: cloud.tencent.com/developer/a…