Static linking
我们在iOS启动优化之从exec()到main()这篇文章中介绍过,动态链接器在搜索依赖项时进行大量的计算和磁盘IO。
静态链接消除了所有dylib搜索的需要-依赖和可执行文件成为一体。
因此,可以将一些库静态地链接到主可执行文件中,减少framework的数量,从而达到优化应用程序启动时间的目的。
那么如何将framework编译为静态库呢?
在Xcode9中,可以通过Build Settings中的MACH_O_TYPE = staticlib来设置,当设置该标志时,链接器会生成静态库。至于通过cocoapods集成的库,我们必须在podfile中创建一个自定义脚本,以便仅在pod安装期间(即依赖项安装期间)为选定的外部库设置此标志,因为cocoapods在每次重新安装时都会为托管库创建新的工程结构。
如果想在Xcode9之前执行静态链接,可以使用libtool。
除了动态库,framework中还可能包含资源(图像、nib等)。我们去掉了动态库,但是不能留下只包含资源的framework。资源包(bundle)是苹果生态系统中包装资源的一种标准方式,可以通过脚本,将framework中的所有资源输出到*.bundle中。然后,通过代码让应用程序能自动使用正确的资源位置。
下面我们来看下,经过静态链接处理之后,启动时间有什么变化?
下面这张图显示了在不同设备上,将26个dylibs进行静态链接处理之后,启动时间的变化。

可以看到在iPhone 5C上的启动时间减少约2秒。在iPad 2上,启动时间得到了更大的改善——相差约4.5秒。
注意:如果有多个静态链接库,注意不要将其与多个动态库链接-这将导致静态库对象在不同的动态库中重复,这可能是一个严重的问题。
Dyld 3
在 iOS 13 之前,所有的第三方 App 都是通过 Dyld 2 来启动 App 的,其加载过程在iOS启动优化之从exec()到main()这篇文章中也介绍过,这里就不在赘述。
Dyld 2加载过程中,会进行大量的计算和I/O操作,因此,会导致 App 启动时间加长。所以苹果开发团队为了加快启动速度,在 WWDC2017上正式提出了 Dyld 3。
Dyld 3 被分为了三个组件:
- 一个进程外的 MachO 解析器。
- 预先处理了所有可能影响启动速度的 search path、@rpaths 和环境变量。
- 然后分析 Mach-O 的 Header 和依赖,并完成了所有符号查找的工作。
- 最后将这些结果创建成了一个启动闭包。
- 这是一个普通的 daemon 进程,可以使用通常的测试架构。
- 一个进程内的引擎,用来运行启动闭包。
- 这部分在进程中处理。
- 验证启动闭包的安全性,然后映射到 dylib 之中,再跳转到 main 函数。
- 不需要解析 Mach-O 的 Header 和依赖,也不需要符号查找。
- 一个启动闭包缓存服务。
- 系统 App 的启动闭包被构建在一个 Shared Cache 中, 我们甚至不需要打开一个单独的文件。
- 对于第三方的 App,我们会在 App 安装或者升级的时候构建这个启动闭包。
- 在 iOS、tvOS、watchOS中,这这一切都是 App 启动之前完成的。在 macOS 上,由于有 Side Load App,进程内引擎会在首次启动的时候启动一个 daemon 进程,之后就可以使用启动闭包启动了。
Dyld 3 把很多耗时的查找、计算和 I/O 操作都预先处理好了,使得启动速度有了很大的提升。
通过测试,Dyld 2 和 Dyld 3启动时间的对比:
launch type | Dyld 2 | Dyld3 |
---|---|---|
warm | 0.722s | 0.731s |
cold | 3.687s | 2.947s |
可以看出,在冷启动时Dyld3 比 Dyld2快20%
Static linking VS Dyld 3
测试结果如下:
launch type | Dyld3 | static |
---|---|---|
warm | 0.731s | 0.679s |
cold | 2.947s | 2.276s |
通过测试结果,我们可以看出,使用静态链接的 App 总是比使用Dyld 3 的 App 启动的更快。