iOS App Size Optimization

背景

通过一份Google的数据,下载大小和转换率有关系[参考]。

1
For every 6 MB increase to an APK’s size, we see a decrease in the install conversion rate of 1%.

前提

要想优化包大小,了解包的构成是前提。对于iOS的App来说,直接下载ipa,解压开就能看到包的构成。

  • 二进制代码
  • 资源bundle - 独立bundle管理 .bundle格式
  • Frameworks - 动态库,12.2以下可能会包含swift动态库,12.2及以上则只要依赖的动态库
  • Plugins - 如果App有Extension等功能,则会有这部分
  • 翻译 - 可能是在main bundle中,也可能是在独立bundle中,以strings文件呈现
  • 散落在main bundle的各种其他资源(没有用独立bundle管理)

通过这里可以看出,按照类型分,包的组成大致是以下几类:

  • 代码 - 二进制+Pluigns中的代码+Frameworks中的动态库
  • 资源:图片/json/db/mp3等等资源
  • 翻译:strings文件(其实可以算资源一种,单独列出来是因为我们的这部分比较大)

方法论

  • 删除 - 删除无用
  • 压缩 - 压缩留在包内的
  • 转移 - 能远程加载的远程加载

这里面要考虑很多东西,比如:不是所有的资源都适合远程加载,因为可能会影响用户体验,而用户体验又可能接着影响跳出率/订单转化 等指标,所以把握不准的时候,最好先用数据说话。

具体方案

方案其实就是,将不同类型的包组成(代码/资源/翻译)和不同方案(删除/压缩/转移)的组合,我们一个一个来枚举。

由于用户只能在AppStore上看到安装大小,所以我们下面讨论的都是基于优化安装大小来说的。

代码

删除代码

  • 通过找出工程中的无用代码来删除

    对于查找无用代码的方法,个人尝试了以下两种:

    1. 反编译或者分析linkmap

      主要就是找出二进制中描述的所有方法,再结合__DATA.__objc_selrefs段找出所有使用到的方法,两者作差集得到无用的方法(selector)。

      结合__DATA.__objc_classlist__DATA.__objc_classrefs作差集找出无用的类。

    2. 编写编译插件接入编译过程分析

      通过编写clang的插件,在遍历AST阶段分析代码之间的调用关系。比如在A类中使用performSelector(method) 那么A和method就有了调用关系。将所有的类信息和类的调用信息收集之后,就可以分析出调用了哪些方法,没有调用哪些方法。

      上述两种方法尝试后,各有优缺点:

      方法1分析耗时很短,但是由于OC的语言特性,会有很多误报。方法2因为定制化程度比较高,如果我们有自己定义的router或者运行时方法,可以分析出来,精确度比方法1高,但是要全源码编译,耗时很长很长。

  • 通过编译配置,让编译器帮我优化代码

    1. Dead Code Strip - YES
    2. Strip Link Product - YES
    3. LTO-incremental(需要在源码编译阶段,而不是在壳工程link阶段配置)
    4. Optimization Level - Os是既快又小,Oz是aggressive size optimization,对size的优化效果比Os好,同样这个配置也是源码阶段配置
    5. Deployment Target设置为12.2,这种方式意味着最低版本支持到12.2,需要考虑自己的App用户占比来决定,好处是12.2以上系统内置swift动态库,不会将Swift动态库打进App包内。
  • 其他
    1. 使用flutter的工程可以考虑做下数据段和代码段剥离
    2. flutter中的符号信息剥离

压缩代码

代码不能压缩,此段略去。

转移代码

理论上代码是不能远程下发的,但是将native业务改成react native然后远程下发,不失为一种手段。

资源

删除资源

和代码类似,资源的删除是基于找到无用的资源。

  • native资源

    因为图片或者其他资源文件的读取方法就那么几个,再考虑到内部封装的方法,所以直接遍历整个工程,找出所有资源文件,再从代码中用正则匹配使用到的资源,很容易能找到无用的资源。

  • flutter资源

    开启tree shaking icon

压缩资源

  • 对于图片可以使用tinypng或者ImageOptim等工具进行压缩(一劳永逸:做到打包流程中)
  • 对于其他资源,都可以进行打包时压缩成7z(7z压缩率比较高),使用时再解压。

转移资源

  • On Demand Resource - 还在实验中,4M的资源下载要花10s左右,如果不是很重要的资源可以尝试
  • 图片等转远程url,使用时下载

翻译

删除翻译

建立使用率上报机制,定期分析无用的翻译进行删除

压缩翻译

因为用的stirngs文件,所以没有考虑过压缩。

转移资源

一个用户不可能用到所有的语种,所以可以按照不同locale的用户占比,将占比低的翻译通过启动时远程下载。

可能会影响用户体验,所以要配置好保底哪些语言,远程下发哪些语言,及时监测业务指标。

持续保持

size的优化肯定不是一次做完就不管的,所以为了长期保持size稳定,我们要建立好长期的管控机制。

  • 分析各个模块size占比

    通过linkmap分析每个模块大小,再结合对应的资源和翻译,算出每个模块的大小

  • 为每个模块设置上限卡点

    size的保持肯定不是一个人或者一个部门就能做到的,给每个模块设定上限,每次打完模块包分析size数据,当数据超标时会无法参与集成。

总结

时间有限,没法讲得很详细,以上只能算是趁自己有空做的简单记录。