Swift Framework 弱链接

对 Framework 进行弱链接,即不保证在运行时能找到链接的符号,这通常适用于一个 Framework 对另一个 Framework 进行弱链接,以减小二进制大小以及提供模块化设计。

下面以 ToasterKitToasterKitCore 为例。ToasterKitCore 提供基础功能,ToasterKit 依赖 ToasterKitCore,但自身不嵌入 ToasterKitCore,而是对其进行弱链接。

创建 ToasterKitCore

首先在 Xcode 中创建一个 Framework,并命名为 ToasterKitCore,其中提供下面的简单代码:

1
2
3
4
5
public enum Toast {
case underdone
case perfect
case burnt
}

对于 ToasterKitCore 无需更多操作,弱链接的设置在 ToasterKit 中进行。

设置 ToasterKit

以相同的方式创建 ToasterKit,并将 ToasterKitCorexcodeproj 文件嵌套到 ToasterKit 项目结构下。(这里主要是为了让 ToasterKit 能找到 ToasterKitCore 的符号,没有 xcodeproj 的话添加编译后的 .framework 文件也可以。)

在 Xcode 的项目设置中,选中 ToasterKit Target,在 Frameworks and Libraries 下添加 ToasterKitCore.framework 并选择 Do Not Embed。然后转到 Build Phases 下,展开 Link Binary With Libraries,将 ToasterKitCore.frameworkStatus 改为 Optional

通过这些设置,链接器在链接时就会对 ToasterKitCore 进行弱链接,不会在编译时要求 ToasterKit 的二进制文件中包含这些符号。

在代码中使用

在 Swift 中,通过 @_weakLinked 属性配合 import 来告知编译器导入的库是弱链接的:

1
@_weakLinked import ToasterKitCore

随后可以同一般的 Framework 一样使用 ToasterKitCore 中的声明:

1
var myToastStatus = Toast.perfect

注意:如果在运行这行代码的时候找不到 ToasterKitCore.framework,App 会以 EXC_BAD_ACCESS(0x0)崩溃。

若要更安全地使用弱引用的符号,可以在使用其符号前添加判断逻辑:

1
2
3
4
5
6
7
8
public struct AdvancedToast {
public init?() {
guard Bundle(identifier: "com.toast.ToasterKitCore") != nil else {
return nil
}
let status = Toast.perfect
}
}

ABI 兼容

如果把这两个 Framework 添加到同一个项目中,ToasterKit 既依赖 ToasterKitCore,同时又是相对独立的。在 ToasterKitCore 需要更新时,只要没有破坏性更改,无需重新编译 ToasterKit

这里说的破坏性更改即破坏 ABI(Application Binary Interface) 兼容性的更改。简单来说,添加内容一般不会破坏 ABI 兼容性,而对已有的声明进行重命名或删除等操作,就会破坏 ABI 兼容性。

使用 @available 标记可以在提醒调用方此声明已弃用的同时保留 ABI 兼容性:

1
2
3
4
@available(*, deprecated, renamed: "NewToast", message: "'Toast' has been deprecated, use 'NewToast' instead.")
enum Toast { ... }

enum NewToast { ... }