在 Swift Package Manager (SPM) 中,实现跨模块共享代码的核心逻辑是:将共享代码提取到一个独立的 target,并将其作为其他 target 的依赖项。
SPM 不允许 testTarget 直接引用另一个 target 的非公开(Internal)代码,除非是在同一个包内使用 @testable import。若要跨模块(即跨 Package)或在多个可执行 target 之间共享,必须遵循以下结构设计:
1. 标准架构:提取“共享库” (Common Library)
不要尝试让两个可执行文件互相引用,而是创建一个专门的底层 target(通常命名为 Core、Shared 或 Kit)。
配置步骤:
- 定义共享 Target:在
Package.swift中创建一个基础 target。 - 定义 Product:如果该共享代码需要被其他 Package 访问,必须将其包装在
libraryproduct 中。 - 添加依赖:在可执行 target 和测试 target 的
dependencies数组中引入该共享 target。
2. 代码实现示例 (Package.swift)
假设你有一个工具类需要在主程序、插件程序以及测试套件中共同使用:
Swift
let package = Package(
name: "MyProject",
products: [
.executable(name: "MainApp", targets: ["MainApp"]),
.executable(name: "HelperTool", targets: ["HelperTool"]),
.library(name: "AppShared", targets: ["AppShared"]) // 可选:供外部包使用
],
targets: [
// 1. 共享代码层
.target(
name: "AppShared",
dependencies: []
),
// 2. 可执行文件 A
.executableTarget(
name: "MainApp",
dependencies: ["AppShared"]
),
// 3. 可执行文件 B
.executableTarget(
name: "HelperTool",
dependencies: ["AppShared"]
),
// 4. 跨模块测试 Target
.testTarget(
name: "SharedLogicTests",
dependencies: ["AppShared"] // 也可以依赖 MainApp,如果需要测试其内部逻辑
)
]
)
3. 访问权限的关键点
在共享代码时,你必须注意 Swift 的访问控制级别:
-
跨 Target 访问:在
AppShared中定义的类、方法或属性必须标记为public或open。默认的internal级别在不同 target 之间是不可见的。 -
测试中的特殊处理:
- 如果
testTarget与被测 target 在同一个 Package:使用@testable import AppShared可以访问internal成员。 - 如果
testTarget需要测试另一个 Package 的代码:只能访问其public接口。
- 如果
4. 高级技巧:测试辅助工具共享 (Test Utilities)
有时你不仅想共享业务代码,还想共享测试工具类(如 Mock 对象、TestCase 基类)。
千万不要把测试工具放在 testTarget 里,因为测试 target 不能被其他 target 依赖。
-
解决方案:创建一个专门的
target命名为TestSupport。 -
配置:
Swift
.target( name: "TestSupport", dependencies: ["AppShared"], path: "Tests/TestSupport" // 手动指定路径,使其逻辑上属于测试分类 ), .testTarget( name: "FeatureTests", dependencies: ["AppShared", "TestSupport"] )
5. 常见问题:资源共享 (Resources)
如果共享的代码包含资源文件(如 JSON、图片、.xcassets):
- 在共享 target 中定义
resources: [...]。 - 访问时使用
Bundle.module。 - 注意:当可执行文件依赖该 target 时,SPM 会自动将资源打包进最终的 Bundle 中,确保运行时能够找到。
总结建议
- 解耦:如果两个可执行文件共用超过 20% 的代码,就应该考虑拆出
Shared模块。 - 瘦身:保持共享模块尽可能轻量,只包含纯逻辑或数据模型,避免引入繁重的第三方依赖,否则会拖慢所有依赖它的 target 的编译速度。