静态库 & 动态库

image.png

  1. 静态库(Static Library):通常会加快冷启动速度(相比于多个动态库)。

  2. 动态库(Dynamic Library):如果是自定义的动态库(Embedded Framework),数量多了会严重拖慢冷启动时间(Apple 建议不超过 6 个)。

1. 静态库 vs 动态库:核心区别

可以用 “盖房子” 来比喻:

1.1 静态库 (.a, .framework 的 Mach-O Type 为 Static)

  • 链接方式(Copy)

    • 在编译链接阶段,链接器(Linker)会将静态库中的代码(指令)完整拷贝到你的 App 可执行文件(Mach-O)中。

    • 比喻:像砌墙。你买了现成的砖块(静态库),直接砌进你的墙体里。房子盖好后(编译完),砖块就成了墙的一部分,以后不需要再单独搬运砖块了。

  • 内存表现

    • 每个 App 都有自己独立的一份代码副本,不共享。

1.2 动态库 (.dylib, .framework 的 Mach-O Type 为 Dynamic)

  • 链接方式(Reference)

    • 链接器不拷贝代码,只在主程序里留一个引用(指针/签名)。等到 App 运行时(Runtime),系统(dyld)才去加载这个库。

    • 比喻:像借工具。你盖房子时只写了个条子“我要用电钻”(引用)。等房子真正要装修(启动运行)时,你才去工具房把电钻拿过来用。

  • 内存表现

    • 系统库(UIKit, Foundation):系统共享一份,所有 App 共用(通过 Shared Cache 技术),速度极快。

    • 自定义库(Embedded Framework):虽然叫动态库,但在 iOS 沙盒机制下,实际上每个 App 也是各自拷贝一份在自己的 Bundle 里,并不能跨 App 共享内存(除非是系统级的)。

特性静态库 (Static)动态库 (Dynamic)
文件格式.a, .framework.dylib, .framework
链接行为编译时拷贝代码运行时加载引用
App 包体积较大(代码在主程中)较小(主程小,但加上库文件总体积差不多)
加载时机随主程序启动加载dyld 单独加载、链接

2. 为什么静态库“不会”增加冷启动时间?

你担心的点可能是:“静态库合并进去了,主 Mach-O 变大了,读取不需要时间吗?”

2.1 虚拟内存的“按需加载”机制

还记得我们上一篇讲的 缺页中断(Page Fault) 吗?

  • 操作系统不会在启动时把整个 100MB 的 Mach-O 文件一次性读入 RAM。

  • 它只会读取启动那一刻需要执行的代码

  • 结论:即使你的静态库让 Mach-O 变成 200MB,只要启动时没调用这个静态库里的代码,它就永远躺在磁盘上,完全不影响启动速度

2.2 减少了 dyld 的工作量(关键优势)

冷启动的 Pre-main 阶段,dyld(动态链接器)非常忙。如果我们使用 10 个自定义动态库:

  • Load dylibs:dyld 要查找、验证签名、映射 10 个文件到内存。

  • Rebase/Bind:每个动态库都有自己的符号表,dyld 需要修正 10 个库里的指针偏移(Rebase)和外部符号绑定(Bind)。这部分是 O(n) 复杂度,消耗大量 CPU。

  • ObjC Setup:需要注册 10 个库里的类、Category。

如果换成静态库:
这 10 个库的代码直接合并到了主程序里。dyld 眼里只有一个文件(主程序)。

  • 不需要重复验证签名。

  • Rebase/Bind 的工作量大幅减少(内部调用变成了直接跳转,不需要 dyld 修正)。

  • 省去了昂贵的 Context Switch 和 I/O 切换。


3. 静态库唯一的“坑”与解法

虽然静态库整体更快,但在一种情况下会导致变慢:代码布局分散

  • 问题
    静态库的代码被简单追加在主程序末尾。如果启动时你需要频繁调用静态库里的 funcA,而 funcA 被链接到了文件的第 1000 页。

    • CPU 就要跳到第 1000 页去读(触发 Page Fault)。

    • 如果静态库很大,且启动代码分散在各个角落,就会导致大量的 Page Fault。

  • 解法(完美闭环)
    这正是 二进制重排(Binary Reordering) 发挥作用的地方!

    • 通过二进制重排,我们可以把静态库里那些“启动时真正用到的函数”,抠出来放到 Mach-O 的最前面(第 1 页)。

    • 结合技静态库合并 + 二进制重排 = iOS 启动速度的极致。


4. 面试满分总结话术

如果面试官问:“静态库和动态库有什么区别?静态库包体积大,会不会拖慢启动?”

建议回答:

  • 区别定义

    • 静态库在编译链接期将代码完整拷贝到可执行文件中;

    • 动态库在编译期只保留引用,运行时由 dyld 加载。

    • 在 iOS 中,除了系统库外,自定义动态库无法在 App 间共享,因此相比静态库优势不明显。

  • 启动性能分析(破除误区)

    • 静态库反而更快。因为 iOS 采用了虚拟内存(分页加载)机制,文件变大并不意味着启动要全部读取,是按需读取的。

    • 相反,使用静态库减少了 dyld 在 Pre-main 阶段加载多个 Image、进行 Rebase(重定位)和 Bind(符号绑定)的耗时。Apple 官方也建议尽量减少自定义动态库的数量(<6个)。

  • 进阶补充

    • “虽然静态库会导致主二进制文件变大,可能导致启动代码分散。但我们可以结合二进制重排技术,将静态库中在启动阶段用到的函数排列到文件头部,从而同时享受到‘减少 dyld 耗时’和‘减少 Page Fault’的双重红利。”

这样回答,既解释了基础,又反驳了误区,还把话题引回了你擅长的二进制重排,体现了技术深度。