4.3 模块别名
4 分钟阅读
原文链接: https://docs.swift.org/latest/documentation/packagemanagerdocs/modulealiasing/
4.3 模块别名
为模块创建别名,以避免你的包或其依赖中各个目标之间的名称冲突。
概述
随着你为包添加依赖,来自不同包的模块之间可能出现名称冲突。Logging、Utils 这类模块名是很常见的例子。为了解决冲突,从 Swift 5.7 起,包管理器在为目标定义依赖时提供了参数 moduleAliases。你可以为本来会冲突的模块定义新的唯一名称,而无需改动任何源代码。
还有以下额外要求需要注意:
- 被别名的模块必须是一个纯 Swift 模块:不支持 ObjC/C/C++/Asm,因为很可能出现符号冲突。同样,应避免使用
@objc(name)。 - 被别名的模块不能是预编译二进制,因为别名会影响名称修饰和序列化,也就是说只能是基于源码的模块。
- 被别名的模块不应传给
NSClassFromString(...)这类运行时调用,因为这类调用会(直接或间接地)把字符串转换成模块中的某个类型,从而失败。 - 如果映射到被别名模块的某个目标包含资源,这些资源应当是资源目录、本地化字符串,或者不需要显式模块名的资源。
- 如果被别名模块所属的产品与另一个产品同名,那么这些产品中最多只能有一个是非自动库类型。
如何使用
模块别名作为目标依赖中的一个字典参数来定义,键是发生冲突的原始模块名,值是用户定义的新的唯一名称:
| |
这会把 MyPackage 包中的 Utils 模块重命名为用户定义的唯一名称,在这个例子里就是 MyPackageUtils;二进制文件的名称会是 MyPackageUtils.swiftmodule。依赖包不需要改动任何源码或清单。
要使用被别名的模块,你的根包可以使用这个新的唯一名称,也就是 import MyPackageUtils,这样也清楚地表明它导入的是 MyPackage 中的工具模块。
接下来看一个例子,更详细地过一遍模块别名可以怎么用。
示例
下面这个名为 App 的包例子从包 swift-draw 导入了模块 Utils 和 Logging。它想再添加一个包依赖 swift-game,并导入该包提供的模块 Utils 和 Game。其中 Game 模块从同一个包导入了 Logging。
App
|— Module Utils (from package ‘swift-draw’)
|— Module Logging (from package ‘swift-draw’)
|— Module Utils (from package ‘swift-game’)
|— Module Game (from package ‘swift-game’)
|— Module Logging (from package ‘swift-game’)
包清单 swift-game
{
name: "swift-game",
products: [
.library(name: "Utils", targets: ["Utils"]),
.library(name: "Game", targets: ["Game"]),
],
targets: [
.target(name: "Game", dependencies: ["Logging"]),
.target(name: "Utils", dependencies: []),
.target(name: "Logging", dependencies: [])
]
}
包清单 swift-draw
{
name: "swift-draw",
products: [
.library(name: "Utils", targets: ["Utils"]),
.library(name: "Logging", targets: ["Logging"]),
],
targets: [
.target(name: "Utils", dependencies: []),
.target(name: "Logging", dependencies: []),
]
}
分析冲突
Utils 模块
swift-draw 和 swift-game 都提供了同名模块 Utils,因而造成冲突。为了解决这个冲突,现在可以用新参数 moduleAliases 来区分它们。
包清单 App
targets: [
.executableTarget(
name: "App",
dependencies: [
.product(name: "Utils",
package: "swift-draw"),
.product(name: "Utils",
package: "swift-game",
moduleAliases: ["Utils": "SwiftGameUtils"]),
])
]
这会把包 swift-game 中的 Utils 模块重命名为 SwiftGameUtils;二进制文件的名称会是 SwiftGameUtils.swiftmodule。
要使用被别名的模块,App 可以引用这个新的、带包限定的名称,也就是 import SwiftGameUtils。它原有的 import Utils 语句仍然会引用包 swift-draw 中的 Utils 模块,符合预期。
注意这里的依赖产品名是重复的,两者都叫 Utils,默认情况下是不允许的。不过,在使用模块别名时这是允许的,只要不产生同名文件即可。这意味着它们必须都是自动库类型,或者其中最多只有一个可以是静态库、dylib、可执行文件,或者任何会以该产品名创建文件或目录的其他类型。
传递依赖中的 Logging 模块
与前面 Utils 的冲突类似,swift-draw 和 swift-game 这两个包都包含同名模块 Logging,因而造成冲突。尽管 App 并没有直接从 swift-game 导入 Logging,这个冲突的模块仍然需要被区分开。
我们可以再次使用 moduleAliases,如下所示。
包清单 App
targets: [
.executableTarget(
name: "App",
dependencies: [
// Utils 模块别名:
.product(name: "Utils",
package: "swift-draw"),
.product(name: "Utils",
package: "swift-game",
moduleAliases: ["Utils": "SwiftGameUtils"]),
// Logging 模块别名:
.product(name: "Logging",
package: "swift-draw"),
.product(name: "Game",
package: "swift-game",
moduleAliases: ["Logging": "SwiftGameLogging"]),
])
]
swift-game 中的 Logging 模块被重命名为 SwiftGameLogging,Game 源文件中对 Logging 的所有引用都会被编译成 SwiftGameLogging。和之前一样,swift-game 包不需要改动任何源码或清单。
如果需要定义更多别名,可以用逗号分隔添加,如下所示。
moduleAliases: ["Utils": "SwiftGameUtils", "Logging": "SwiftGameLogging"]),
覆盖模块别名
如果上游定义的模块别名值在下游发生冲突,可以通过链式覆盖来解决:在下游的 moduleAliases 参数中加一个条目,用冲突的别名值作为键,并提供一个唯一的值。由于包标识符对该包是唯一的,按惯例把它用作新模块别名的前缀,有助于避免更多冲突,因为它可以成为该模块一个普遍认可的唯一名称。