大多数浏览器和
Developer App 均支持流媒体播放。
-
认识可合并库
了解可合并库如何结合静态库和动态库的优点,帮助提高 App 的工作效率和运行时性能。了解如何在交付尽可能小的 App 的同时实现更快的开发。我们将向你展示如何在 Xcode 15 中采用可合并库,并分享使用代码的最佳实践。
章节
- 0:55 - Static and dynamic libraries overview
- 1:45 - Meet mergeable libraries
- 5:41 - Benefits of mergeable libraries
- 9:01 - Automatic merging in Xcode
- 13:03 - Manual merging in Xcode
- 16:40 - Debug mode
- 19:07 - Considerations
- 23:59 - Recommendations
- 25:11 - Wrap-up
资源
相关视频
WWDC22
-
下载
♪ ♪
Cyndy:大家好 我是 Cyndy Languages and Runtimes 团队的一名编译器工程师 在本次讲座中 我们将讨论可合并库 这是一种由静态链接器驱动的 构建和发布库的新模式 我将与大家分享可合并库是如何 让你的 App 更快地构建和运行的
随后 我将演示如何 在 Xcode 15 中启用可合并库 最后 我将介绍使用可合并库时的 注意事项和相关建议 开始之前 我先简要介绍一下 静态库和动态库 这部分内容会让你看到 可合并库的优势 静态库是目标文件的集合 在构建时 静态链接器从这些 库中找到需要使用的API 并将代码复制到 App 的 二进制文件中
由于代码已被复制 因此 在构建后就不需要该库了 如果静态库中的代码发生变化 或者使用了更多的库 就会导致构建时间变慢 这是静态库归档和链接到 App 的方式造成的 也使得迭代构建和调试速度变慢 此时 就可以使用动态库 来避免这种情况 动态库通常被称为 dylibs 它们是 Xcode 中框架目标的 二进制文件类型
框架的代码不会被复制到 可执行文件中 相反 静态链接器则会将库的 安装路径记录到 App 的 二进制文件中 以便稍后使用 任何不在 Apple SDK 中的框架 都必须嵌入到 App 软件包中 关键区别在于 当动态库被添加或更新时 静态链接器不需要复制代码 因此能加快构建速度 但是 它增加了 App 运行时使用的复杂性 这就是需要动态链接器之处 当 App 启动时 名为 dyld 的动态链接器 必须找到并加载框架依赖项 包括这些框架依赖的库
随着使用的库越来越多 内存消耗和 App 启动时间 会持续增加 如果考虑到 Apple SDK 的依赖性 App 通常会加载数百个框架 为此 我们的平台对系统库 进行了大量优化 但这并不适用于 嵌入到 App 中的框架 因此 在决定使用 静态库还是动态库时 需要进行一些权衡
动态库对构建时间的影响很小 但对启动时间的影响很显著; 静态库对启动时间的影响很小 但对构建时间的影响很大 因此 我们一直建议你衡量 哪种库最适用于你的 App 有了可合并库 你就无需过多权衡了 可合并库充分发挥了 两种链接策略的优点 我将介绍可合并库 如何优化性能和开发 随意想象一个二进制映像 比如一个可执行文件 这个二进制文件所依赖的框架 被提供给静态链接器 这些依赖可以成为可合并库 而链接后的输出可以成为 合并后的二进制文件 但是 是什么使得这些 依赖库可以合并呢? 这可以通过它们的构建方式来解释 任何动态库都可以 被构建为可合并库 当静态链接器创建库时 它也会生成元数据 元数据在二进制文件中 增加了二进制文件的整体大小 当它被用作链接依赖时 它允许链接器 以类似于静态库的方式处理该库 有了元数据 库的用户可以选择 像普通动态库那样 静态链接或合并相关库 合并后的二进制输出可以是 一个可执行文件 如 App 也可以是另一个动态库 如框架 合并与静态库的链接方式类似 最后 你会得到一个包含库片段的 二进制文件 而输出的二进制文件 仍是相同的文件类型 合并在 Xcode 15 中 是全新的功能 新实现的静态链接器 让这一点成为了可能 它通过使用新的链接器选项来工作 首先 要合并的库是使用选项 -make_mergeable 构建的 这一步告诉链接器记录元数据 接下来 对于合并后的二进制文件 链接器使用该元数据以及库 通过选项 -merge_library 或 -merge_framework 生成最终输出 Xcode 会为你处理这些细节 但你可以在检查构建日志时 看到这些选项被应用 但是合并比仅仅链接好在哪里呢? 我们一起算算合并后的大小 首先 库和它们的元数据 是不需要的 合并后可以移除 因此 唯一的重点是 合并后二进制文件的大小 合并时 链接器可以删除 所有库中的重复内容 如字符串 举个例子 它删除了 多余的符号引用、 Objective-C 选择器 和 objc_msgsend 桩代码 于是整个 App 软件包 的体积变得更小 最终二进制文件的 映像类型也保持不变 这意味着可以应用任何 已经支持的链接器优化 这对 App 的启动也有积极影响 当加载的框架较少时 就会减少 dyld 和内核在启动 App 时 需要做的工作 还可以减少内存的使用 保证较好的用户体验 但我们知道 将代码分离成库 对于有效的开发和维护至关重要 有了可合并库 你就可以两者兼得 可合并库只需最少的代码和配置更改 即可实现这一点 当你采用更新的框架时 这一点也会得到很好的扩展 让我们回顾一下之前 关于动态链接的示意图 所有这些嵌入式框架都可以合并 因为链接器可以为它们生成元数据 我们可以创建一个框架 来合并其他库的内容 因此 你最终只需要嵌入一个框架 dyld 只需要加载包含 所有嵌入式框架片段的库 通过这种方式 合并可以 极大地简化大型依赖链 这就是可合并库所能实现的效果 接下来我们来聊聊 如何启用可合并库 在 Xcode 中有两种方式 可以实现库的合并 我先介绍一下最简单的自动合并 然后再介绍手动合并 以便你 随时控制应该合并的内容 我将描述可合并库如何在 调试模式下让你体验最佳构建时间 接着 我将分享当你需要对可合并库 进行调试和符号化时 应该注意的事项 自动合并通知构建系统合并 作为嵌入式框架目标的 所有直接依赖项 对于 App 目标特别有用 让我来演示一下 以 Swift 和 C++ Forest 项目为例 有一个 App 目标 它链接了四个框架 其中一个是 SwiftUI 来自 Apple SDK 另外三个分别是 ForestBuilder、 ForestUI 和 Forest 都是在项目中构建的 启用自动合并后 这三个 Forest 框架 将变得可合并 SwiftUI 保持不变 因为它是一个系统库 在链接 App 时 这些框架将 直接合并到 App 二进制文件中 这意味着在启动时不需要这些框架 并且可以从磁盘中移除 让我们看看如何在 Xcode 中 打开这个功能
在项目中 我已经点击了 Swift 和 C++ App 目标 并进入了 Build Settings 标签栏 我需要更新 MERGED_BINARY_TYPE 的构建设置 使用过滤器文本框进行搜索
“Create Merged Binary” 正是我要更新的内容 它是映射到设置 MERGED_BINARY_TYPE 的选项 我将点击该设置 并将其值更新为 Automatic 完成!
可合并库设置在常规链接选项下 它们显示在名为 “Linking - Mergeable Libraries” 的独立部分中 非常方便 当 App 启用库合并时 库片段将直接链接到 App 二进制文件中 这将产生与静态库类似的性能 但可合并库的导出 将保留在 App 中 App 导出符号通常并不适用 其会对程序大小和构建时间 产生负面影响 为了避免这种情况 请使用 链接器选项 -no_exported_symbols 此举可以在 Xcode 中使用 “-Wl, -no_exported_symbols” 更新 Other Linker Flags 如果你的 App 需要 App 扩展的入口点 请使用列出这些符号的 导出列表来严格控制 你可以在使用导出符号文件的 常规链接选项下进行设置 此时 静态链接器能够最大化地 进行体积优化 例如进行死代码剥离 这是自动合并 但有时 只有部分框架需要合并在一起 Xcode 支持手动合并 手动合并是一种指定 欲合并库的细粒度方法 当一些依赖库需要保留在 App 软件包中时 这种方法非常有用 在稍后讨论注意事项时 我将对此进行扩展 通过在总体目标上设置 MERGED_BINARY_TYPE = manual 来启用手动合并 将 MERGEABLE_LIBRARY 设置为 YES 可以识别最终合并产品中的库 而对于应该保留在磁盘上的库 则保持默认设置 即将 MERGEABLE_LIBRARY 设置为 NO 让我们回到 Swift 和 C++ Forest 项目 继续演示 我们从头开始 不做任何 与自动合并相关的修改 我们仍然有 App 目标 和它链接的四个框架 但现在 我还要考虑测试 项目中有一个 XCTest 目标 和一个支持框架 测试也依赖于 forest 框架 在项目的框架之间 依赖关系是交织在一起的 在这个例子中 我们有 一个 XCTest 目标 但在你的项目中 可能会有 像 App 扩展这样的目标 它们会创建一个类似的依赖图
为了使用可合并库 我将隔离三个 forest 框架 的 App 依赖
我将创建一个框架 ForestKit 它合并了 App 所需的库 同时也能满足我的测试依赖
ForestKit 被认为是一个组库 因为它封装了 App 和测试都依赖的可合并库
由于启用了手动模式 我会明确设置哪些框架是可合并的 在本例中 可合并框架是 ForestBuilder、ForestUI 和 Forest
这些依赖项 将合并到 ForestKit 通过减少需要加载的库 我的 App 在启动时间和 软件包大小方面都有所改善 让我们在 Xcode 中打开它
我已经重启了项目 并删除了自动合并的所有设置 我将从创建框架目标开始 合并其他框架 这是我的组库 ForestKit 点击 Targets 部分的底部 即可完成该操作
我在弹出的模板中的 macOS 标签栏中 使用过滤器文本框找到框架模板
我将产品名称设置为 ForestKit 然后点击完成
在这个框架中 我要合并 除了 Forest Test Support 框架以外的所有库 但由于我的依赖关系 是交织在一起的 所以我将暂时链接所有的依赖关系 为此 我将更新 Link Binary with Libraries 构建阶段 使用加号添加框架
在库弹出窗口后 我会点击 Forest 框架 然后按住 SHIFT 和 DOWN 键来 突出显示 Xcode 项目中的其他框架
接下来 我需要在此目标上 启用手动合并 进入 Build Settings 标签栏 再次查找 Create Merged Binary 在过滤器文本框中键入 MERGE
这次我将值设置为 Manual 以上就是我需要在组库目标上 完成的所有设置 我可以通过每个框架目标的 构建设置来选择要合并的库 在目标部分 我将从 Forest 框架开始 在 Build Settings 标签栏中 点击 Build Mergeable Library 该选项映射到构建设置 MERGEABLE_LIBRARY 我将该值更新为 Yes
我需要为 ForestUI 和 Forest Builder 做同样的设置
ForestKit 框架 已合并完成 但我需要更新一些依赖项 因为我已经创建了一个 封装了大部分动态库的框架 此时需要确保我的 App 和测试 准确链接到 ForestKit 首先 为了修复 App 单击 Swift 和 C++ App 目标
我将返回到 Build Phases 标签栏 并向下找到 Link Binary with Libraries 在这里我将删除不必要的框架 选择 Forest 并按住 SHIFT 和 UP 键 来多选 ForestUI 和 ForestBuilder 最后删除它们 最后一步是测试 我会点击 XCTest 目标 进入 “Link Binary with Libraries”下的 Build Phases 标签栏
双击表格中的名称 即可删除 Forest 框架
然后使用加号添加 ForestKit
弹出窗口后 双击 ForestKit
这就是配置手动合并的方法 对于 Swift 和 C++ Forest 项目 我一直在发布模式下工作 这就是将库合并 然后从磁盘中删除的操作 然而 合并会带来 构建时间上的增加 可能会给开发带来高昂的成本 这与静态库的构建时间行为类似
为了支持 Xcode 中的迭代开发 链接器不会在调试模式下进行合并 构建系统会告诉链接器重新导出库 重新导出是一个链接器选项 它允许代码在一个动态库中实现 但显示为在另一个库中实现 换句话说 这意味着所有库的 API 都可以通过合并目标来实现 比如你的 App 扩展或测试 这与动态库在构建时间上的 优势类似 在启动时 dyld 会重定向 所有对重新导出的库的引用 而不是期望它们直接来自 合并后的二进制文件 这确实意味着在调试的情况下 可合并的库会保留在磁盘上
说到调试 让我们看看 一个可能存在于可合并库中的符号 这里是一个函数 它接受一个 整数后会返回其平方结果 这是构建的代码 然而 我们知道这不是 机器执行的代码 相反 这段代码要经过许多转换 一切都很顺利 但检查代码中的 错误就不是那么简单了 这就是 Xcode 支持符号化的原因 符号化是将这些机器指令 关联到原始源代码的过程 这对于理解崩溃日志 或剖析和调试你的代码非常有用 合并后的二进制文件如何工作?
当你启用合并功能时 源代码位置信息 仍会从原始库中保留 这意味着你的调试体验保持不变 但请记住 当显示库信息时 例如堆栈跟踪 将显示合并二进制文件的路径 这些信息会在崩溃日志、 Instruments 内部和 调试器中显示 现在是时候考虑你的项目 要如何采用可合并库了 在许多情况下 启用可合并库 只需要一些 Xcode 设置 但是你需要注意其中一些因素 我将介绍五个需要考虑的重要主题 首先 我将介绍你应该如何处理 可合并库上已有的依赖 然后 我将介绍什么是自动链接 以及它如何与可合并库协同工作 如果你使用 dlopen 或 bundle 接口等 运行时查找 API 我还会介绍一些限制 由于合并是由 Xcode 15 中的 静态链接器驱动的 我还会介绍它与 上一代产品的主要区别 最后一个考虑因素是 那些有兴趣将他们的框架 发布给其他开发者的人
对于库的依赖 让我们回到 dyld 的工作示意图 如果一个可合并库的 依赖库没有被合并 例如其他可执行文件 它们将需要更新为 依赖于合并后的框架 因为可合并的库会从磁盘中移除 另一种情况是 App 依赖于自动链接 自动链接是编译器默认开启的选项 当编译器在源代码中 发现模块导入时 它会检测框架依赖关系 然后传递给链接器 因此 如果你从一个 可合并库中导入模块 可能会导致动态链接问题 不过你不需要禁用自动链接 解决方法与之前相同: 链接到合并的框架
最常见的方法是在 Link Binary with Libraries 构建阶段 添加合并的框架 如果已经有可合并的框架 则将其移除 否则 dyld 将无法为你的 App 加载正确的框架
大多数开发者不需要使用 动态链接 API 比如 dlopen 但如果你想使用 这些输入路径 也需要指向合并后的框架目标
同样 资源查找也会受到 库合并的影响 这是因为运行时的期望 在 Swift 中 bundle 是运行时 加载框架软件包的 API Objective-C 中的对应 API 是 NSBundle 的 bundleForClass 这些 API 用于处理框架的资源 而无需考虑 bundle 的结构
在 iOS 12 之前 运行时需要 框架的二进制文件 来发现软件包 但可合并的框架在运行时 不会有二进制文件 好消息是! 在 iOS 12 中 添加了一个钩子 来启用这种情况下的查找 这意味着如果你依赖于 软件包查找支持 则应该更新你的最小部署版本 到 iOS 12 或更高版本 才能使用可合并库 但如果你不依赖这些 API 则可以使用新的链接器选项 -no_merged_libraries_hook 来禁用这种支持 这样你就不需要 更新 App 的部署版本了 如果你合并的框架 不包含软件包资源 你可能也不需要软件包钩子 如果是这种情况 你应该考虑添加这个选项 以提高启动时的性能 在本次会议中 我提到了 一些新的链接器选项 这些选项仅适用于新实现的链接器 但是如果你仔细看一下工具链 会发现其实有两个静态链接器 旧的链接器仍然支持向后兼容 最值得注意的是 该链接器 仍可构建 armv7k 但新链接器则不行 最后一个支持 armv7k 架构的 平台是 watchOS 8 如果你不需要部署到 watchOS 8 或更早版本 请将部署版本升级到 watchOS 9 以使用新的链接器 我已经介绍了如何在你的 App 中构建和使用可合并库 但如果你想发布一个可合并库 供他人使用 该怎么办呢? 你可以在 Swift Package Manager 或 Xcode 中 创建一个 XCFramework 这样你就可以构建包括 元数据在内的框架以供发布 当其他开发者使用该框架时 他们可以自行决定是否启用合并 可合并元数据会使 dylib 的大小翻倍 但这不会影响 App 的大小 因为在构建 App 后 元数据会与可合并库一起被丢弃 否则 在 App 中嵌入时 元数据会被剥离 以防止在将其嵌入 App 时过于臃肿 我已经介绍了可合并库的 一些细微差别 现在我将分享我们的建议 在合并后的二进制文件中 设置依赖关系是无缝采用的关键 这对于任何链接依赖都是必要的 如果你在脚本阶段将库提供给 那些需要二进制文件的工具 这一点便尤为重要 静态链接器只合并直接依赖项 因此 为了包含更多可合并库 应该将它们设置为显式链接依赖 合并设置指示 Xcode 构建系统 从磁盘中移除框架的二进制文件 如果不是有意设置的 这会导致一些副作用 因此我们建议在 Xcode 目标级别启用它们 最后 为了在优化性能的同时 获得最大的生产力收益 请考虑将所有可合并静态库 更新为动态库 可合并库提供了便利性和灵活性 在自动和手动工作流程之间 你可以随意重组和添加可合并库 并将必要的库保留在磁盘上 这种灵活性在逐步采用 或剖析时非常有用 当应用于框架和可执行目标时 可合并库提供了大小、 构建和运行时改进 通过使用自动配置 你可以让构建系统 合并所有直接的框架依赖 但当你需要选择合并哪些依赖项时 可以通过手动模式来完成 最后 当更新项目 以使用可合并的库时 要确保这些库的所有依赖库 都依赖于合并后的二进制文件 而不是被移除的库 有关可合并库的文档 请查看“使用可合并库 的项目配置” 要进一步了解关于静态链接 和动态链接的信息 请查看讲座“快速链接: 缩短构建和启动时间” 我们很期待看到可合并库 在你的项目中发挥出其独特作用 感谢你的观看
-
-
9:18 - Enabling library merging
MERGED_BINARY_TYPE
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。