iOS性能---安装包瘦身

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

虽然苹果官方一直在提高最大的可执行文件大小,在 iOS 13 还取消了强制的 OTA 限制,但是超过200MB会默认请求用户下载许可(可在 设置 - iTunes Store与App Store - App下载 中配置),并且iOS13以下的超过200MB无法使用OTA,影响了整体更新率。
如果App还要适配iOS8之前的版本,苹果还规定了主二进制__TEXT段的大小不能超过60MB,否则上传构建时会被打回。

资源瘦身优化

  1. 压缩资源

    • 无损压缩,利用tinypng进行图片压缩
    • 格式png,jpg,gif可以替换成webp
    • 动画可以用lottie-ios
    • 小图或表情图可替换为iconFont,可以用iconfont(iconfont图标是一种.ttf格式的字体)
    • 大图可替换为svg,用SVGKitSVGKLayeredImageView进行显示,(SVG是一种基于XML语法的图像格式,全称是可缩放矢量图)
  2. 删除无用资源,使用LSUnusedResources

    LSUnusedResources是一款可以检测Xcode工程中没有使用的切图的Mac端工具

  3. 删除重复资源并修改引用,使用fdupes

    • 原理:fdupes是 Linux 平台的一个开源工具,由C语言编写 ,文件比较顺序是大小对比 > 部分MD5签名对比 > 完整MD5签名对比 > 逐字节对比
    • 使用:
      1
      2
      3
      4
      5
      6
      7
      8
      //通过 Homebrew 安装 fdupes:
      brew install fdupes

      //查看目标文件夹下的重复文件:
      fdupes -Sr 文件夹 // 查看文件夹下所有子目录中的重复文件及大小
      fdupes -Sr 文件夹 > 输出地址.txt // 将信息输出到txt文件中

      //根据输出内容,一般情况下,相同文件仅保留一份,修改对应的引用即可。
  4. 图片资源放入.xcassets

    • 尽量将图片资源放入 Images.xcassets 中,包括 pod 库的图片。 Images.xcassets 中的图片加载后会有缓存,提升加载速度,并且在最终打包时会自动进行压缩(Compress PNG Files),再根据最终运行设备进行 2x 和 3x 分发。

    • 对于内部 Pod 库中的资源文件,我们可以在 Pod 库里面的 Resources 目录下新建 Asset Catalog 文件,命名为 Images.xcassets,移入所有图片文件,接着手动修改该 SDK 的 podspec 文件指定使用该 Images.xcassets。

  5. 动态下载资源

    • 有些非必要的文件资源可以放在服务器,结合本地缓存策略,比如主题、皮肤、音乐这样的资源

代码优化

  1. 扫描未使用代码

    • 基于源码扫描 fui
      基本思路是对源码文件进行字符串匹配。例如将 A *a、[A xxx]、NSStringFromClass(“A”)、objc_getClass(“A”) 等归类为使用的类,@interface A : B 归类为定义的类,然后计算差集。

    • 通过 AppCode 查找无用代码 AppCode
      查找出 AppCode 中无用的类、无用的方法甚至是无用的import,但是无法扫描通过字符串拼接方式来创建的类和调用的方法。

    • LinkMap结合Mach-O
      LinkMapSymbols中会列出所有方法、类、block及它们的大小,通过获取 LinkMap即可以获得方法和类的全集;再通过MachOView获得使用过的方法和类,两者的差值就是我们要找寻的未使用代码。参考iOS微信安装包瘦身

      • 获取 LinkMap
        Build Setting - Write Link Map File 设置为 Yes,接着设置 Path to Link Map File 指定文件存放位置,即可在每次编译后获取对应的 LinkMap 文件。

      • MachOView
        MachOView是一款开源软件,可以查看 Mach-O 文件的内容。OC 的方法都会通过objc_msgSend来调用。可以通过 Mach-O 查看 __objc_selrefs 这个section来获取selector参数的,另外__objc_classrefs__objc_superrefs 这两个section可以获取调用过的类和父类。

  2. 扫描重复代码并优化,可以使用PMD
    通过 brew 命令安装

    1
    2
    3
    4
    5
    6
    //PMD 是一个代码静态扫描工具,直接通过 brew 命令安装。
    $ brew install pmd
    //安装完成后,通过 PMD-CPD 即可得到重复代码信息,格式如下:
    //其中,--files 用于指定文件目录,--minimum-tokens 用于设置最小重复代码阈值,--format 用于指定输出文件格式,支持 xml/csv/txt 等格式,这里建议使用 xml,方便查看 。
    $ pmd cpd --files 扫描文件目录 --minimum-tokens 70 --language objectivec --encoding UTF-8 --format xml > repeat.xml
    //根据生成的XML文件,根据file标签信息就能定位到重复代码位置。
  3. 进行framework瘦身(未支持bitcode),
    使用lipo命令拆分库,保留需要的指令集,发布版本中删除 i386、x86_64 是模拟器的指令集,只保留 armv7 和 arm64
    静态库指令集信息查看:

    lipo -info libname.a(或者libname.framework/libname)

    静态库拆分:

    lipo 静态库文件路径 -thin CPU架构 -output 拆分后的静态库文件路径

    静态库合并:

    lipo -create 静态库1文件路径 静态库2文件路径… 静态库n文件路径 -output 合并后的静态库文件径

编译选项配置优化

  • Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES,目前Xcode已经默认打开的,老项目注意检查;
  • 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO,Other C Flags添加-fno-exceptions;
  • Strip Debug Symbols During Copy设置为YES,这个是将那些拷贝进项目包的三方库、资源或者 Extension 的 Debug Symbol 去除掉。这个选项没有前置条件,只需在Release模式下开启,否则就不能对三方库进行断点调试和符号化了;
  • Build Settings -> Optimization Level有几个编译优化选项,release版应该选择Fastest, Smalllest,这个选项会开启那些不增加代码大小的全部优化,并让可执行文件尽可能小;
  • Enable BitCode设置为YES;

App Thinning

  • App Thinning包括: App Slicing, Bitcode, On Demand Resources

    App Slicing工作原理:

    把App安装包上传到App Store后,Apple分析处理服务会自动对安装包切割为不同的应用变体,用户下载安装包时,系统会根据设备型号下载对应的应用变体。也就是根据目标设备加载asset catalog 中的特定的图片资源(注:只有Assets.xcassets内的图片文件有效,在bundle的中图片是无效的)。(例:iPhone7 plus只加载3x图)。根据目标设备分配所需的加载可执行体系结构。(例: iPhone5(真机32位处理器)加载armv7s架构)。

    Bitcode工作原理:

    是Xcode编译打包的一种中间码,在包含Bitcode配置的程序被上传到App Store之后,App Store也可以对其进行编译和链接。同时,Bitcode允许苹果后期重新优化程序的二进制执行文件。苹果会根据下载应用的用户手机指令集类型生成该指令集的二进制文件,进行下发下载。
    开启Bitcode之后,用户在App Store下载的包体积可以小一些。需要注意的是:开启Bitcode之后,集成的其他第三方库也需要全部支持Bitcode。

    On Demand Resources工作原理:

    App不是包含整个资源库,可以根据需要下载或删除其中的一部分。通过将代码段标记为ODR,开发人员将能够指定在什么时候需要什么代码。这些部分在需要时会自动从App Store下载,而在不再需要时会删除。
    产生的用户体验:
    应用程序尺寸较小,因此应用程序下载速度更快,从而改善了首次启动体验。
    用户浏览您的应用程序时,可根据需要在后台下载按需资源。
    当不再需要按需资源并且磁盘空间不足时,操作系统将清除它们。

安装包组成

  • _CodeSignature(签名文件):存放文件的hash列表,它的作用是用来判断一个应用程序是否完好无损,能够防止不小心修改或损坏资源文件
  • 一些.bundle文件:bundle是一种标准化的层次结构,保存了可执行代码以及代码所需要的资源。bundle文件可以理解为一个资源包,用于存储图片、音频、文本、nib文件等,方便在其他项目中引用包内的资源。bundle包是静态的,不参与编译,也就意味着,bundle 包中不能包含可执行的文件。它仅仅是作为资源,被解析成为特定的二进制数据。从.app的包里可以看到,.bundle文件基本上都是一些SDK制作的
  • Assets.car:把放在Assets.xcassets中的图片(除了AppIcon和LaunchImage,这两种图片是直接放在包中的)打包后统一压缩成一个Assets.car的文件,减小包的大小。
  • 一些.nib文件:使用xib创建的文件。
  • .plist:一些属性列表文件。
  • Frameworks:Framework 是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
  • 一些.png、.jpg、.mp3、.mp4等形式的图片和音视频资源。

参考文章

深入探索 iOS 包体积优化
iOS性能优化安装包瘦身
iOS瘦身——移除无用资源的LSUnusedResources源码分析与优化