WWDC Quick Look 💓 By SwiftGGTeam
Meet mergeable libraries

Meet mergeable libraries

观看原视频

Highlight

Xcode 15 引入 Mergeable Libraries,通过静态链接器在构建时生成元数据,使动态库既能像静态库一样合并到最终二进制中以减小体积、加速启动,又能在开发时保持动态库的构建速度优势。

核心内容

静态库与动态库的权衡

开发者在选择库类型时长期面临两难:

静态库在构建时将代码复制到 App 二进制中。好处是启动快,不需要 dyld 加载。坏处是每次代码变更都要重新链接,迭代开发和调试变慢。

动态库(dylib)在构建时只记录安装路径,代码不复制。好处是构建快,添加或更新库时代价小。坏处是启动时 dyld 必须找到并加载所有依赖,框架越多,内存占用和启动时间越长。

00:55

Apple 平台对系统库做了大量优化,但嵌入到 App bundle 中的第三方框架享受不到这些优化。因此历史上需要针对具体应用测量权衡。

Mergeable Libraries 的工作原理

Mergeable Libraries 的核心是在动态库构建时生成额外的元数据(metadata)。

04:06

当静态链接器创建动态库时,使用 -make_mergeable 选项记录元数据。这些元数据嵌入在二进制内部,使链接器在后续链接时可以将该库当作类似静态库处理。

合并时使用 -merge_library-merge_framework 选项,链接器利用元数据将库的内容合并到最终输出中。合并后的二进制保持原有的文件类型(可执行文件仍是可执行文件,框架仍是框架)。

05:10

合并带来的好处:

  • 体积减小:链接器可以跨库去重字符串、符号引用、Objective-C selector、objc_msgSend stub
  • 启动加速:dyld 和内核需要加载的框架减少,内存占用降低
  • 构建保持快速:Debug 模式下不执行合并,保持动态链接的构建速度

Debug 模式的特殊处理

Debug 模式下,链接器不会真正合并库,而是使用 reexport(重新导出)机制。

16:41

Reexport 允许代码实现驻留在一个动态库中,但对外显示为另一个库实现。这意味着 App Extension 或测试目标只需依赖合并后的目标,就能访问所有库的 API,构建速度与纯动态链接相当。

详细内容

自动合并配置

在 Xcode 中启用自动合并只需一个设置:

MERGED_BINARY_TYPE = automatic

09:02

在 Xcode 界面中:

  1. 选中 App Target
  2. 进入 Build Settings
  3. 搜索 “Create Merged Binary”
  4. 将值设为 “Automatic”

自动合并会将所有直接依赖的嵌入框架目标变为 mergeable,系统框架(如 SwiftUI)不受影响。

减少导出符号以优化体积

合并后,库的导出符号会保留在 App 中。App 通常不需要导出符号,这会对体积和构建时间产生负面影响。

10:04

在 Other Linker Flags 中添加:

-Wl,-no_exported_symbols

如果 App 需要为 App Extension 保留入口点,使用 Export Symbols File 精确控制保留哪些符号。

手动合并配置

当只有部分框架需要合并时,使用手动模式:

// 在合并目标上
MERGED_BINARY_TYPE = manual

// 在要合并的库目标上
MERGEABLE_LIBRARY = YES

// 在保留的库目标上(默认)
MERGEABLE_LIBRARY = NO

11:10

手动合并的典型场景:创建一个 “group library”(如 ForestKit),将多个内部框架合并进去,App 和测试目标都链接这个 group library。

运行时查找 API 的限制

如果代码使用 dlopenNSBundle.bundleForClass 等运行时查找 API,需要注意:

19:26

  • dlopen 的路径需要指向合并后的框架目标
  • Bundle(for:) / bundleForClass: 在 iOS 12 之前依赖框架二进制来发现 bundle,合并后的框架在运行时没有独立二进制
  • 最低部署版本需要 iOS 12+ 才能使用 bundle 查找支持
  • 如果不依赖这些 API,可以添加 -no_merged_libraries_hook 选项避免部署版本限制

分发 Mergeable 库

可以通过 Swift Package Manager 或 Xcode 创建 XCFramework 来分发 mergeable 库。

23:17

  • Mergeable 元数据大约使 dylib 体积翻倍
  • 元数据在 App 构建后会被丢弃,不影响最终 App 体积
  • 嵌入 App 时会自动剥离元数据,防止体积膨胀

核心启发

  1. 将静态库迁移为 Mergeable 动态库

    • 做什么:把项目中大量使用的静态库改为动态库并启用 mergeable
    • 为什么值得做:保留静态库的启动性能优势,同时获得动态库的构建速度优势
    • 怎么开始:将库目标从 Static Library 改为 Framework,设置 MERGEABLE_LIBRARY = YES
  2. 为大型 App 启用自动合并

    • 做什么:在 App Target 上开启 Automatic Merging
    • 为什么值得做:一个开关即可减少嵌入框架数量,降低包体积和启动时间
    • 怎么开始:在 Build Settings 中搜索 “Create Merged Binary”,设为 Automatic
  3. 创建 Group Library 简化依赖图

    • 做什么:将多个内部框架合并到一个 group framework 中
    • 为什么值得做:App Extension 和测试目标只需链接一个框架,减少 dyld 加载开销
    • 怎么开始:创建新的 Framework target,启用 manual merging,将子框架设为 mergeable
  4. 优化导出符号进一步减体积

    • 做什么:使用 -no_exported_symbols 或精确的导出符号文件
    • 为什么值得做:App 通常不需要导出符号,去掉后链接器可以更有效地进行死代码剥离
    • 怎么开始:在 Other Linker Flags 中添加 -Wl,-no_exported_symbols

关联 Session

评论

GitHub Issues · utterances