iOS启动优化之Static linking vs Dyld 3

2,406 阅读4分钟

原文地址

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 Dyld 3
warm 0.722s 0.731s
cold 3.687s 2.947s

可以看出,在冷启动时Dyld3 比 Dyld2快20%

Static linking VS Dyld 3

测试结果如下:

launch type Dyld 3 static
warm 0.731s 0.679s
cold 2.947s 2.276s

通过测试结果,我们可以看出,使用静态链接的 App 比Dyld 3的App启动的更快。


扫一扫关注我们,get更多iOS技能