4.3 模块别名

原文链接: https://docs.swift.org/latest/documentation/packagemanagerdocs/modulealiasing/

4.3 模块别名

为模块创建别名,以避免你的包或其依赖中各个目标之间的名称冲突。

概述

随着你为包添加依赖,来自不同包的模块之间可能出现名称冲突。Logging、Utils 这类模块名是很常见的例子。为了解决冲突,从 Swift 5.7 起,包管理器在为目标定义依赖时提供了参数 moduleAliases。你可以为本来会冲突的模块定义新的唯一名称,而无需改动任何源代码。

还有以下额外要求需要注意:

  • 被别名的模块必须是一个纯 Swift 模块:不支持 ObjC/C/C++/Asm,因为很可能出现符号冲突。同样,应避免使用 @objc(name)。
  • 被别名的模块不能是预编译二进制,因为别名会影响名称修饰和序列化,也就是说只能是基于源码的模块。
  • 被别名的模块不应传给 NSClassFromString(...) 这类运行时调用,因为这类调用会(直接或间接地)把字符串转换成模块中的某个类型,从而失败。
  • 如果映射到被别名模块的某个目标包含资源,这些资源应当是资源目录、本地化字符串,或者不需要显式模块名的资源。
  • 如果被别名模块所属的产品与另一个产品同名,那么这些产品中最多只能有一个是非自动库类型。

如何使用

模块别名作为目标依赖中的一个字典参数来定义,键是发生冲突的原始模块名,值是用户定义的新的唯一名称:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    targets: [ 
        .target(
            name: "MyTarget",
            dependencies: [ 
                .product(
                    name: "Utils",
                    package: "MyPackage",
                    moduleAliases: ["Utils": "MyPackageUtils"]
                )
            ]
        )
    ]

这会把 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 参数中加一个条目,用冲突的别名值作为键,并提供一个唯一的值。由于包标识符对该包是唯一的,按惯例把它用作新模块别名的前缀,有助于避免更多冲突,因为它可以成为该模块一个普遍认可的唯一名称。