阅读 887

iOS打包速度优化

iOS编译的过程可以简化为:预处理-编译生成中间代码-汇编器生成汇编代码-生成机器码-链接-生成可执行文件。在我们打包的时候,可以简单的认为打包时间由:编译时间+链接时间+生成调试信息时间。由于链接是没有缓存的,而且只能用单核进行,所以它的耗时主要取决于单核性能和磁盘读写速度,所以这一块除了更换设备,没有什么优化空间。对于调试信息,我们日常测试包可以选择,不生成调试信息(dSYM文件),这样可以减少20秒左右的时间。可以看到,我们可以减少的时间主要放在编译阶段。

1 减少编译文件和资源

由于在编译过程中,所有依赖的文件、系统库、第三方库都会被引入编译,因此一个比较直接的方式就是减少文件和资源的数量,保持代码整洁,对于不需要使用的库和文件,及时进行清理。

下图是车商的部分依赖系统库,可以看到还有Twitter这种,所以文件在日积月累的情况下,是会有不少冗余依赖的。

车商依赖的系统库.png

2 减少重复编译

现有项目中,我们的引入依赖文件都是使用#import。由于#import 的本质就是把被依赖头文件的内容拷贝到自己的头文件中来,因此头文件 A 中实际上包含了 M N 个头文件的内容,也就需要 M N 次文件 IO 和相关处理。当项目中每增加一个依赖头文件 A 的文件,就会重复一次上述的 M * N 复杂度的过程。

以前我们会使用PCH文件。PCH 文件的好处是,这个文件中的头文件只会被编译一次并缓存下来,然后添加到项目中所有的头文件中去。上述问题倒是解决了,但很智障的一点是,所有文件都会隐式的依赖所有 PCH 中的文件,而真正需要被全局依赖的文件其实非常少。因此实际开发中,更多的人会把 PCH 当成一种快速 import 的手段,而非编译性能的优化。前文解释过,PCH 文件一旦发生修改,会导致彻彻底底,完完整整的项目重编译,从而降低编译速度。正是因为 PCH 的副作用甚至抵消了它带来的优化,苹果已经默认不使用 PCH 文件了。

用来取代 PCH 的就是 Clang modules 技术,对于开启了这一选项的项目,我们可以用 @import 来替代过去的 #import,比如: @import UIKit;等价于 #import <UIKit/UIKit.h>。抛开自动链接 framework 这些小特性不谈,Clang modules 可以理解为模块化的 PCH,它具备了 PCH 可以缓存头文件的优点,同时提供了更细粒度的引用。

还有一个小技巧是使用向前声明。在.h文件中使用@class CLASSNAME,而不是#import CLASSNAME.h。将引入头文件的时机尽量延后,只在需要的时候才引入,这样可以减少类使用者所需要引入头文件的数量。而且前向声明也解决了两个类的循环引用问题。

3 缓存编译

对常用的工具类进行打包(Framework/.a)打包成Framework或者静态库,这样编译的时候这部分代码就不需要重新编译了。在车商跟车e估打包的对比过程中我发现,车商的Realm这个第三方库,每一次编译会花费很长时间,而车e估则没有这个花费,原来是车e估没有使用cocoapods来管理Realm,而是直接引入静态库,所以花费时间大大减少。对于稳定的第三方库,或者我们编写的工具,我们也可以采用这种方式来降低打包花费。

其实如果我们每次打包之前不clean的话,我们会发现打包时间其实会缩短很多的。这是因为Xcode本身使用了增量编译的方式,每次编译会重新编译修改文件和修改文件的引用,但是对于没有改动的部分则会使用之前缓存的编译结果。但是这个增量编译并不稳定,我之前在打包过程中出现过增量编译没有打包最新代码的问题。

因此出现了一些第三方缓存工具,比较容易接入的是CCache这个工具。CCache是一个能够把编译的中间产物缓存起来的工具,而且比较稳定,因此可以打打提高编译速度。但是CCache不支持clang module,因此需要关闭项目中的Enable Modules,同时对于cocoapods管理的第三方库,也需要处理clang module的问题。具体使用方法可以参考文章:使用CCache让打包飞起来

4 修改工程设置

一般来说,我们的持续集成工具主要是用来给产品经理或者测试人员使用,用来体验功能或者验证 Bug,除非是需要上架 App Store,否则并不需要关心运行时性能。然而在手机上使用的 Release 模式,默认会开启各种优化,这些优化都是牺牲编译性能,换取运行时速度,对于上架的包而言无可厚非,但对于那些 Daily Build 包来说,就显得得不偿失了。

因此,加速打包的思路和优化的思路是完全互逆的,我们要做的就是关闭一切可能的优化。

Valid Architectures
这个选项是指定处理器的指令集。对于功能测试来说,我们可以指定只打包测试机对应的指令集。

armv7|armv7s|arm64|arm64e都是ARM处理器的指令集 i386|x86_64 是Mac处理器的指令集

指令集对应的机型:
2018 A12芯片arm64e : iphone XS、 iphone XS Max、 iphoneXR
2017 A11芯片arm64: iPhone 8, iPhone 8 Plus, and iPhone X
2016 A10芯片arm64:iPhone 7 , 7 Plus, iPad (2018)
2015 A9芯片arm64: iPhone 6S , 6S Plus
2014 A8芯片arm64: iPhone 6 , iPhone 6 Plus
2013 A7芯片arm64: iPhone 5S
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4

模拟器32位处理器测试需要i386架构, 模拟器64位处理器测试需要x86_64架构, 真机32位处理器需要armv7,或者armv7s架构, 真机64位处理器需要arm64架构。

Optimization Level
关闭编译优化。优化的基本原理是牺牲编译时性能,追求运行时性能。常见的优化有编译时删除无用代码,保留调试信息,函数内联等等。因此提升打包速度的秘诀就是反其道而行之,牺牲运行时性能来换取编译时性能。对于日常测试打包,可以将Optimize level 的Release状态下的值改成 O0,表示不做任何优化。

Debug Information Format
不生成 dYSM 文件,Release状态下的值修改为DWARF。

5 采用新构建系统(New Build System)

苹果从Xcode 9开始推出了新构建系统(New Build System),并在Xcode 10使用其为默认构建系统来替代旧构建系统(Legacy Build System)。采用新构建系统能够减少构建时间。

简要介绍一下原理,对于旧构建系统,当我们构建一个程序的时候,会明确所需要构建的所有Target,这些Target之间的依赖关系,以及这些Target构建的顺序。采用顺序会造成多处理器系统资源的浪费,从而表现为编译时间的浪费,解决这个问题的方式就是采用并行编译,这也是新构建系统优化的核心思想。详细了解新构建系统,探究Xcode New Build System对于构建速度的提升。

6 使用脚本

使用脚本打包要比使用Xcode快,而且节省中间的界面操作时间。

总结

上面就是总结的一些提高打包速度的一些方法,应该根据项目实际情况选择合适的方法优化。
对于项目改动较小且比较容易实现的方式主要是:减少编译文件和资源、 将部分第三方库改为静态库、工程配置修改这几种方式。

关注下面的标签,发现更多相似文章
评论