在 Swift Package Manager (SPM) 中,理解 target、product 和 dependencies 的区别是构建模块化项目的基石。简单来说,它们分别代表了代码的构建单元、输出的交付物以及外部的支撑力。
1. 核心概念辨析
Target:构建的“原子”单位
Target 是 SPM 的最小构建单元。它定义了一组源代码文件以及它们如何被编译。
- 代码隔离:每个 Target 都有自己的命名空间。
- 类型:可以是
target(应用代码)、executableTarget(命令行工具)或testTarget(单元测试)。 - 内部依赖:Target 之间可以相互依赖。
Product:对外暴露的“门面”
Product 是 Package 对外提供的输出产物。它是 Target 的逻辑组合,决定了其他 Package 能看到什么。
- library:静态或动态库,供其他项目链接。
- executable:可执行二进制文件。
- 可见性控制:一个 Package 可能有 10 个 Target,但如果 Product 只包含其中 1 个,那么外部调用者只能看到这 1 个。
Dependencies:外部的“源泉”
Dependencies 定义了当前 Package 运行所需的外部资源。
- Package 级别依赖:指向 Git 仓库地址或本地路径。
- Target 级别依赖:指定某个具体的 Target 需要用到哪个 Package 里的哪个 Product。
2. 三者的关系模型
你可以把 SPM 想象成一个工厂:
- Target 是工厂里的生产线,负责加工零件。
- Product 是工厂展厅里的最终商品,由一条或多条生产线组装而成。
- Dependencies 是工厂的原材料供应商。
3. 多模块依赖的规划策略
当项目规模扩大时,合理的规划能避免编译时间过长和依赖地狱。
A. 遵循“功能分层”原则
将项目划分为四个核心层级,避免循环依赖:
| 层级 | 职责 | 依赖限制 |
|---|---|---|
| App/Feature 层 | 业务逻辑、UI 界面 | 依赖 Domain 和 Utility |
| Domain 层 | 业务模型、协议(Interface) | 仅依赖 Utility |
| Utility 层 | 网络库、扩展工具、存储 | 不依赖业务逻辑 |
| Core/Third-party 层 | 外部库、二进制 target | 处于依赖链最底层 |
B. 接口与实现分离 (Interface Segregation)
为了提高编译速度,可以采用“接口包”模式:
- 创建一个
FeatureInterfaceTarget,仅包含协议和数据模型。 - 创建一个
FeatureImplementationTarget,包含具体的逻辑。 - 其他模块只依赖
Interface,这样当Implementation的内部代码修改时,不会触发其他模块的重新编译。
C. 慎用动态库 (Dynamic vs Static)
在定义 product 时:
.library(name: "MyLib", targets: ["MyLib"])默认通常是静态链接。- 如果你的多模块项目中,多个动态库同时依赖同一个静态库,会导致代码冗余和符号冲突。
- 建议:除非有特殊需求(如插件化或减小包体积),否则让 SPM 自动决定链接方式。
4. 实战:Package.swift 配置示例
Swift
let package = Package(
name: "MyModularApp",
products: [
// 外部可见的模块
.library(name: "CoreUI", targets: ["CoreUI"]),
.library(name: "NetworkLayer", targets: ["NetworkLayer"])
],
dependencies: [
// 外部依赖
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0")
],
targets: [
// 基础工具层
.target(name: "NetworkLayer", dependencies: ["Alamofire"]),
// UI 组件层
.target(name: "CoreUI", dependencies: []),
// 业务层:依赖内部的两个模块
.target(name: "HomeFeature", dependencies: ["CoreUI", "NetworkLayer"]),
// 测试层
.testTarget(name: "HomeFeatureTests", dependencies: ["HomeFeature"])
]
)
5. 规划避坑指南
- 避免循环依赖:如果 A 依赖 B,B 依赖 C,C 又依赖 A,SPM 会直接报错。此时应提取公共部分到
Core模块。 - 隐藏内部 Target:不要把所有的 Target 都包装进 Product。只暴露必要的接口,保持 Package 的整洁。
- 合理使用 Binary Target:对于闭源库或极其庞大的第三方库,使用
binaryTarget可以极大地提升编译速度。