返回文章列表

Swift Package Manager 系列 03|Package.swift 里最重要的几个概念

真正难的不是语法,而是理解 products、targets、dependencies 在工程边界里分别意味着什么

很多人第一次打开 Package.swift,第一感觉都会是:文件不长,语法也不复杂。

但真正一上项目就容易懵,因为难点根本不在“语法像不像 Swift”,而在于里面几个核心概念其实对应的是工程结构,而不是普通配置项。

如果只把它当成“填几个字段的清单”,你会觉得每个词都认识,但一改就容易乱。
真正要搞懂的是这些概念分别在回答什么问题。

我觉得最重要的不是把所有参数背下来,而是先抓住这几个核心角色:

  • package
  • products
  • dependencies
  • targets

一、Package.swift 不是描述文件列表,而是在描述模块关系

这是理解它的第一步。

很多人下意识会把它当成:

  • 项目配置文件
  • 依赖安装文件
  • 或者“Swift 版 Podspec”

但从工程角度看,Package.swift 更像一份模块关系说明书。
它在告诉工具链:

  • 这个包对外提供什么
  • 它依赖外部哪些包
  • 内部由哪些 target 构成
  • 这些 target 之间如何关联

所以它关注的不是“有哪些文件”,而是“这些代码应该如何被组织成模块并被别人使用”。

二、products 回答的是:这个包想把什么能力交给外部

这是很多人一开始最容易忽略的点。

target 是内部构建单元,product 才是对外可消费的结果。

也就是说,product 回答的问题是:

这个包最终打算把什么东西暴露给使用者?

这背后是一个很重要的设计信号。
如果你对 product 没概念,就很容易把包内部结构和外部使用方式混在一起。

一个包内部当然可以有多个 target,但外部未必需要知道全部这些内部拆分。
所以 product 本质上是在做对外接口包装。

从这个角度理解,你就会更自然地去思考:

  • 哪些内部 target 是实现细节
  • 哪些能力真的需要暴露
  • 外部依赖者看到的应该是什么层级

三、dependencies 回答的是:这个包向外部借用了哪些能力

很多人一看到 dependencies,就只想到“装第三方库”。

但从工程角度看,它更重要的意义是:

这个模块的边界之外,它依赖了什么外部世界。

这会直接影响:

  • 模块是否足够轻
  • 它的编译成本高不高
  • 它未来是否容易复用
  • 它会不会把宿主工程也一起绑定进某些外部依赖里

一个模块如果外部依赖过多,往往会带来两个问题:

  • 它不再轻量
  • 它的边界也开始变得不清晰

所以看 dependencies 时,不要只想“能不能装上”,更要想“这个模块为什么需要依赖这些东西”。

四、targets 回答的是:内部代码应该如何被组织和编译

这是 Package.swift 里最容易被当成“文件夹映射”的一部分,但它其实远不止这个作用。

target 真正对应的是:

  • 一组一起被编译的代码
  • 一个明确的依赖单元
  • 一个内部模块边界

当你定义 target 时,你其实是在决定:

  • 哪些代码应该一起演进
  • 哪些实现应该放在同一个编译单元里
  • 哪些测试应该对应这个边界

所以 target 不是“目录分组的另一种写法”,而是内部模块化的最小承载单元之一。

如果你把 target 拆得太碎,工程会变重。
如果你把 target 做得太大,边界又会模糊。
这就是为什么 Package.swift 看似只是配置,实际上在承载很多架构判断。

五、为什么很多人会把 producttarget 混淆

因为在简单包里,它们经常是一一对应的。

例如:

  • 一个 target
  • 一个 library product

这时很容易误以为两个概念差不多。
但一旦项目稍微复杂一点,你就会发现:

  • 一个 package 可以有多个 target
  • 多个 target 可以服务于一个 product
  • 某些 target 只是内部实现,不应该直接暴露

所以我的经验是:
把 target 理解成内部结构,把 product 理解成外部出口。

这样思路会一下清楚很多。

六、测试 target 为什么也很重要

很多人看 Package.swift 时,会把测试 target 当成附属配置。
但从模块化思维看,它其实很重要。

因为一个模块值不值得独立存在,很大程度也体现在:

  • 它能不能被独立测试
  • 它的依赖边界是否足够清晰
  • 它是否能在不依赖宿主 App 的情况下验证核心行为

如果一个模块每次测试都还得依赖半个宿主工程,那它虽然名义上是 Package,实际上边界可能并不独立。

七、一个更实用的阅读顺序

如果我第一次打开一个陌生项目的 Package.swift,我通常会按这个顺序看:

  1. products 先看它对外暴露什么
  2. dependencies 再看它向外部借了什么
  3. targets 最后看内部是怎么拆的

这个顺序的好处是:
你先看边界,再看内部,而不是一上来陷进实现细节。

八、结论:Package.swift 的难点不在语法,而在它其实是在描述工程边界

如果只用一句话总结,我会说:

Package.swift 最重要的不是“会不会写配置”,而是你能不能理解 productdependencytarget 背后各自在表达什么边界关系。

一旦按这个角度去理解,这个文件就不再只是配置,而是模块设计的浓缩表达。