SPM 快速入门教程 – wiki基地


Swift Package Manager (SPM) 快速入门教程:从概念到实践

引言:拥抱 Swift 包管理的新时代

在现代软件开发中,依赖管理是一个至关重要的环节。它帮助开发者组织、共享和复用代码模块(即“包”或“库”)。对于 Swift 生态系统而言,Apple 推出的 Swift Package Manager (SPM) 扮演着官方、跨平台的包管理工具角色。SPM 不仅让依赖管理变得更加 Swift 原生,而且与 Xcode 紧密集成,极大地简化了项目的构建和分发流程。

本教程将带你从零开始,详细了解 SPM 的核心概念、基本用法,包括如何创建 Swift 包、如何添加依赖、如何在项目中使用包,以及如何在命令行和 Xcode 中进行操作。无论你是 Swift 开发新手,还是从其他包管理器(如 CocoaPods 或 Carthage)迁移过来,本教程都将为你提供一个快速掌握 SPM 的全面指南。

我们将涵盖以下主要内容:

  1. 什么是 Swift Package Manager (SPM)? – 理解其目的、优势及在 Swift 生态中的地位。
  2. SPM 的核心概念 – 包、产品、目标、依赖等。
  3. 创建一个 Swift 包 – 使用命令行工具初始化不同类型的包。
  4. Package.swift 解析 – 理解包清单文件的结构和作用。
  5. 添加和管理依赖 – 如何在 Package.swift 中声明外部库,并理解版本控制。
  6. 在 Swift 项目中使用 SPM – 命令行构建、运行与测试。
  7. SPM 与 Xcode 的集成 – 在 Xcode 项目中添加和使用 Swift 包。
  8. 关键概念深挖 – 目标与产品、依赖解析等。
  9. 常用 SPM 命令详解
  10. 最佳实践与技巧

准备好了吗?让我们开始 Swift 包管理的旅程!

1. 什么是 Swift Package Manager (SPM)?

Swift Package Manager 是一个用于自动化分发 Swift 代码并将其集成到你的构建系统中的工具。它由 Apple 开发并维护,并且是开源的。SPM 的目标是让 Swift 代码的共享和复用变得简单、高效且跨平台(支持 macOS, Linux, iOS, tvOS, watchOS 等)。

SPM 的主要优势:

  • Swift 原生: 使用 Swift 语言编写的包清单文件 (Package.swift),与其他 Swift 代码风格一致。
  • 跨平台: 可以在任何支持 Swift 的平台上使用 SPM。
  • 与 Swift 工具链集成: 作为 Swift 工具链的一部分发布,无需额外安装(通常随 Swift 或 Xcode 一起安装)。
  • 与 Xcode 深度集成: Xcode 原生支持 SPM,提供图形界面管理依赖,简化开发流程。
  • 去中心化: 不需要中心仓库,可以直接引用 Git 仓库的 URL。
  • 依赖解析强大: 能够处理复杂的依赖关系图,并自动解决版本冲突。
  • 支持二进制依赖: 除了源代码,SPM 也支持引入二进制框架 (.xcframework)。

简单来说,SPM 就是 Swift 世界里的依赖管理工具,类似于前端的 npm/yarn,Ruby 的 Bundler,Java 的 Maven/Gradle 等。

2. SPM 的核心概念

理解 SPM 的几个核心概念对于入门至关重要:

  • 包 (Package): SPM 的基本组织单元。一个包是一个包含一个或多个目标(Target)、产品(Product)和依赖项的目录。通常,一个 Git 仓库就是一个包。包的根目录下必须有一个 Package.swift 文件。
  • 产品 (Product): 包暴露给外部使用的可构建的输出。产品有两种主要类型:
    • 库 (Library): 可被其他 Swift 包或应用导入并使用的代码模块。
    • 可执行文件 (Executable): 可以直接运行的程序。
  • 目标 (Target): 包中的一个构建模块。一个目标定义了其源文件所在的目录、对其他目标或外部包的依赖。一个包可以包含多个目标,通常分为以下几种:
    • 普通目标 (Regular Target): 包含库或可执行文件的源代码。
    • 测试目标 (Test Target): 包含用于测试其他目标的单元测试或集成测试代码。
    • 可执行目标 (Executable Target): 包含一个 main.swift 文件或带有 @main 属性的类型,可以构建成一个可执行文件。
  • 依赖 (Dependency): 一个包所依赖的其他 Swift 包。SPM 会负责下载这些依赖包的源代码,并在构建时将其集成到你的项目中。

3. 创建一个 Swift 包 (命令行)

SPM 最直接的使用方式是通过命令行工具 swift package。让我们创建一个新的 Swift 包。

打开终端应用,导航到你想要创建包的目录。

创建一个库包:

如果你想创建一个可以被其他项目引用的库,使用 init 命令并指定类型为 library (这是默认类型,可以省略):

bash
mkdir MyAwesomeLibrary
cd MyAwesomeLibrary
swift package init --type library

执行完毕后,SPM 会生成以下文件和目录结构:

.
├── Sources/
│ └── MyAwesomeLibrary.swift
├── Tests/
│ ├── MyAwesomeLibraryTests/
│ │ └── MyAwesomeLibraryTests.swift
│ └── XCTestsManifests.swift
├── .gitignore
└── Package.swift

  • Package.swift: 包的清单文件,描述了包的名称、目标、产品和依赖。
  • Sources/: 存放库的源代码文件。默认生成了一个与包同名的 .swift 文件。
  • Tests/: 存放测试代码。默认生成了一个测试目标目录和测试文件。
  • .gitignore: 包含 SPM 生成的忽略项(如 .build 目录)。

创建一个可执行包:

如果你想创建一个可以直接运行的命令行工具,使用 init 命令并指定类型为 executable

bash
mkdir MyCommandLineTool
cd MyCommandLineTool
swift package init --type executable

生成的结构略有不同:

.
├── Sources/
│ └── MyCommandLineTool/
│ └── main.swift
├── Tests/
│ └── MyCommandLineToolTests/
│ └── MyCommandLineToolTests.swift
├── .gitignore
└── Package.swift

  • Sources/MyCommandLineTool/main.swift: 可执行程序的入口点。
  • 注意,对于可执行包,源文件通常放在一个与包同名的子目录中,这个子目录就是可执行目标。

文件组织约定:

SPM 遵循约定大于配置的原则。默认情况下:

  • 源文件位于 Sources/ 目录下。
  • 测试文件位于 Tests/ 目录下。
  • 一个目录下的所有 .swift 文件通常组成一个目标。
  • 如果 Sources/ 下只有一个目录(如 Sources/MyLibrary/),则该目录下的所有源文件组成一个名为 MyLibrary 的目标。
  • 如果 Sources/ 下有多个目录(如 Sources/ModuleA/, Sources/ModuleB/),则每个目录对应一个同名目标。

这些约定可以在 Package.swift 中修改,但遵循约定会让事情变得更简单。

4. Package.swift 解析

Package.swift 是 SPM 包的核心。它是一个 Swift 文件,使用 Swift 语言来定义包的结构和属性。让我们看看一个典型的 Package.swift 文件(以库包为例):

“`swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: “MyAwesomeLibrary”, // 包的名称
platforms: [
// 支持的平台和最低版本,可选。如果未指定,默认支持所有平台,最低版本由 Swift 工具链决定。
// .macOS(.v10_15),
// .iOS(.v13),
// .tvOS(.v13),
// .watchOS(.v6),
// .visionOS(.v1)
],
products: [
// 定义包提供的产品
.library(
name: “MyAwesomeLibrary”, // 产品名称
targets: [“MyAwesomeLibrary”] // 产品包含的目标
),
],
dependencies: [
// 声明包的依赖项
// .package(url: “https://github.com/apple/swift-argument-parser”, from: “1.2.0”),
],
targets: [
// 定义包中的目标
.target(
name: “MyAwesomeLibrary”, // 目标名称
dependencies: [
// 目标依赖于包内的其他目标或包外的依赖项
// .product(name: “ArgumentParser”, package: “swift-argument-parser”), // 依赖于 swift-argument-parser 包的 ArgumentParser 产品
]
),
.testTarget(
name: “MyAwesomeLibraryTests”, // 测试目标名称
dependencies: [
“MyAwesomeLibrary”, // 测试目标依赖于其测试的普通目标
// 其他测试所需的依赖
]
),
]
)
“`

Package.swift 的关键部分:

  • // swift-tools-version: 5.9: 这一行非常重要,它指定了构建该包所需的 Swift 工具链版本。SPM 会根据这个版本来解析 Package.swift 文件。如果你的 Swift 版本低于此要求,构建将失败。
  • import PackageDescription: 导入 SPM 定义的 DSL (Domain Specific Language) 模块,用于构建 Package 对象。
  • let package = Package(...): 创建并配置一个 Package 实例。
    • name: 包的唯一名称。
    • platforms: (可选)声明该包支持的最低平台版本。这有助于依赖该包的项目确定兼容性。
    • products: 定义包暴露给外部使用的产品。一个产品通常对应一个库或一个可执行文件。
      • .library(name:targets:): 定义一个库产品。name 是库的名称,targets 是构建该库所需的(一个或多个)目标。
      • .executable(name:targets:): 定义一个可执行产品。name 是可执行文件的名称,targets 是包含入口点(如 main.swift)的目标。
    • dependencies: 声明该包所依赖的外部 Swift 包。这是添加第三方库的地方。后面会详细讲解依赖的声明方式。
    • targets: 定义包内部的构建模块。每个目标对应源文件目录,并可以声明其内部依赖。
      • .target(name:dependencies:path:sources:resources:plugins:settings:checksum:): 定义一个普通目标(库或可执行目标的组成部分)。
        • name: 目标名称,通常与源文件目录名相同。
        • dependencies: (可选)该目标依赖的其他目标或外部包的产品。
        • path: (可选)指定源文件目录,默认是 Sources/ 下与目标同名的目录。
        • sources: (可选)指定该目标包含的具体源文件或排除的文件。
        • resources: (可选)指定需要包含在构建产物中的资源文件(图片、数据等)。
        • settings: (可选)编译器设置。
      • .executableTarget(...): 定义一个可执行目标,通常包含 main.swift。是 .target 的一个快捷方式,用于表明这是一个可执行的入口。
      • .testTarget(name:dependencies:path:sources:resources:settings:): 定义一个测试目标。dependencies 中通常包含要测试的普通目标。

5. 添加和管理依赖

SPM 允许你轻松地将其他 Swift 包作为依赖添加到你的项目中。依赖通常是托管在 Git 仓库中的 Swift 包。

在你的 Package.swift 文件中,找到 dependencies 数组,并添加你的依赖。添加依赖的标准格式是 .package(url: ..., from: ...) 或其他版本控制方式。

常见的依赖声明方式:

  • 基于语义版本控制 (Semantic Versioning – SemVer): 这是最推荐的方式。指定一个最低版本,SPM 会自动使用该版本或任何后续的兼容版本(根据 SemVer 规则)。
    • .package(url: "https://github.com/vendor/package.git", from: "1.2.0"): 依赖版本 1.2.0 或更高,直到下一个主要版本发布前 (< 2.0.0)。这是最常用且安全的选项,因为它允许获取补丁和次要更新,同时避免不兼容的主要版本更改。
    • .package(url: "...", .upToNextMinor(from: "1.2.0")): 依赖版本 1.2.0 或更高,直到下一个次要版本发布前 (< 1.3.0)。
    • .package(url: "...", .exact("1.2.3")): 只依赖精确的 1.2.3 版本。不推荐,除非有特殊需求,因为它会阻止获取任何更新。
    • .package(url: "...", "1.2.0"..<"1.3.0"): 依赖 1.2.01.3.0 之间的版本(不包含 1.3.0)。
  • 基于分支 (Branch): 依赖特定分支的最新提交。
    • .package(url: "https://github.com/vendor/package.git", branch: "develop"): 依赖 develop 分支的最新代码。适合开发中的依赖,但不适合发布,因为代码可能不稳定。
  • 基于提交 (Revision): 依赖 Git 仓库的特定提交(commit hash)。
    • .package(url: "https://github.com/vendor/package.git", revision: "abcdef1234567890abcdef1234567890"): 依赖精确的某个提交。极少使用,通常用于锁定一个已知有问题的版本或尚未打标签的版本。

示例:添加一个常用库

假设我们想添加 Swift Argument Parser 这个库来构建命令行工具。它的 GitHub 地址是 https://github.com/apple/swift-argument-parser。查阅其发布版本,发现最新稳定版是 1.2.3

在你的可执行包的 Package.swift 中,修改 dependencies 数组:

swift
dependencies: [
// 声明包的依赖项
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"), // 依赖版本 1.2.0 或更高,直到 < 2.0.0
],

然后,找到你的可执行目标(例如 MyCommandLineTool),在它的 dependencies 数组中声明对该外部包产品的依赖:

swift
targets: [
.executableTarget(
name: "MyCommandLineTool",
dependencies: [
// 依赖于 swift-argument-parser 包的 ArgumentParser 产品
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
// ... 其他目标(如测试目标)
]

依赖解析与 Package.resolved

当你添加或修改依赖后,SPM 需要执行一个“依赖解析”过程。这个过程会计算出所有依赖包的精确版本,并确保它们之间没有冲突。解析结果会写入一个 Package.resolved 文件(通常在 .build/ 或与 Package.swift 同级,具体位置取决于 SPM 版本和使用方式,Xcode 会放在 .xcodeproj/project.xcworkspace/xcshareddata/SwiftPackageManager/)。

Package.resolved 文件记录了每个依赖包被解析到的精确版本或提交。 提交并分享 Package.resolved 文件是强烈推荐的,因为它保证了团队成员或在不同时间构建项目时,都能使用完全相同的依赖版本,从而实现可重复构建。

6. 在 Swift 项目中使用 SPM (命令行)

在命令行环境中,SPM 提供了一系列命令来构建、运行和测试你的包。

导航到包含 Package.swift 文件的包根目录。

  • 下载和解析依赖:

    bash
    swift package resolve

    这个命令会根据 Package.swift 文件下载或更新依赖,并生成/更新 Package.resolved 文件。通常在 swift buildswift run 之前隐式调用。

  • 构建包:

    bash
    swift build

    这个命令会编译你的包及其所有依赖。构建产物会放在 .build/ 目录下。对于库包,会生成库文件;对于可执行包,会生成可执行文件。

    • 可以通过 --configuration release--configuration debug 指定构建配置。
  • 运行可执行包:

    bash
    swift run [可执行产品名称]

    如果你创建的是一个可执行包,可以使用 swift run 直接运行它。如果包只有一个可执行产品,可以省略产品名称。例如,对于上面的 MyCommandLineTool 包:

    bash
    swift run MyCommandLineTool

    这个命令会先执行构建(如果需要),然后运行指定的可执行产品。

  • 运行测试:

    bash
    swift test

    这个命令会构建并运行包中的所有测试目标。它会执行 Tests/ 目录下测试目标中的所有测试方法,并输出测试结果。

  • 清除构建产物:

    bash
    swift package clean

    删除 .build/ 目录,清除所有构建生成的文件。在遇到奇怪的构建问题时,执行此命令有时会有帮助。

  • 更新依赖:

    bash
    swift package update

    根据 Package.swift 中定义的规则(例如 .from: "1.2.0"),检查依赖是否有新版本可用,并更新到满足条件的最新的兼容版本。这会更新 Package.resolved 文件。

  • 生成 Xcode 项目文件:

    bash
    swift package generate-xcodeproj

    注意:这个命令在现代 SPM 和 Xcode 版本中已不再推荐或必要。 Xcode 已经原生支持直接打开 SPM 包目录或向现有 Xcode 项目添加 SPM 包,无需生成单独的 .xcodeproj 文件。)
    在早期 SPM 版本中,此命令用于为包生成一个临时的 Xcode 项目文件,方便在 Xcode 中查看和编辑代码。现在,你可以直接在 Xcode 中打开包含 Package.swift 的目录。

7. SPM 与 Xcode 的集成

现代版本的 Xcode 对 SPM 提供了深度原生支持,这使得在 iOS、macOS 等项目中使用 Swift 包变得异常便捷。

方法一:创建一个新的 Xcode 项目并集成 SPM 包

当你创建新的项目时 (File > New > Project),Xcode 提供了一个模板叫做 “Swift Package”。使用这个模板创建的项目本质上就是一个 Swift 包,Xcode 会自动识别 Package.swift 文件,并提供图形界面来管理依赖、构建和运行。

方法二:向现有 Xcode 项目添加 Swift 包依赖

这是最常见的用法。你可以在现有的 iOS、macOS 等应用或框架项目中添加 Swift 包作为依赖。

  1. 打开你的 Xcode 项目 (.xcodeproj.xcworkspace)。
  2. 在 Xcode 菜单栏选择 File > Add Packages...
  3. 在弹出的搜索框中,输入你要添加的 Swift 包的 Git 仓库 URL。你可以直接粘贴 GitHub/GitLab/Bitbucket 等仓库的 URL。
  4. Xcode 会自动检测并显示该仓库中的 Swift 包。
  5. 在右侧的 “Dependency Rule” 下拉菜单中,选择你想要的依赖版本规则。通常推荐使用 “Up to Next Major Version”。你也可以选择特定的版本、分支或提交。
  6. 点击 “Add Package” 按钮。
  7. Xcode 会开始下载和解析依赖。完成后,弹出一个窗口让你选择将该包的产品(Library 或 Executable)添加到你的哪个目标 (Target)。例如,一个 iOS 应用项目可能有一个与应用同名的主目标和一个测试目标,你可以选择将库产品添加到主目标和测试目标中。
  8. 选择好目标后,点击 “Add to Targets”。

添加完成后,你会看到:

  • 在项目导航器 (Project Navigator) 的项目文件 (.xcodeproj) 下,新增了一个 “Package Dependencies” 部分,列出了你添加的所有 Swift 包。
  • 当你选中你的项目文件,然后选中一个具体的目标 (Target),在 “General” 或 “Build Phases” 选项卡中,你会看到添加的 Swift 包产品已经被自动添加到 “Frameworks, Libraries, and Embedded Content” 或 “Link Binary With Libraries” 中。
  • Xcode 会自动生成并更新 Package.resolved 文件,它存储在你的 .xcodeproj.xcworkspace 目录中(具体路径可能是 .xcodeproj/project.xcworkspace/xcshareddata/SwiftPackageManager/Package.resolved)。务必将这个文件提交到你的版本控制系统!

现在,你就可以在你的项目中导入并使用这个 Swift 包提供的代码了,就像使用任何其他模块一样:

“`swift
import ArgumentParser // 如果你添加了 swift-argument-parser

// 使用 ArgumentParser 提供的功能
// …
“`

Xcode 会负责调用 SPM 工具链进行依赖的下载、构建和链接,整个过程对开发者来说非常顺畅。构建你的 Xcode 项目时,SPM 依赖也会被自动构建。

8. 关键概念深挖

为了更好地利用 SPM,我们深入理解几个关键点:

目标 (Target) 与产品 (Product) 的区别与联系:

  • 目标 (Target) 是包的内部构建单元。它定义了哪些源文件属于它,以及它依赖包内的哪些其他目标或包外的哪些产品。目标本身通常不对外可见。例如,一个复杂的库可能由多个内部目标组成,每个目标处理一部分功能。
  • 产品 (Product) 是包暴露给外部使用的接口。它指定了由哪些目标组合而成,形成一个可导入的库或可运行的程序。其他包或应用依赖的是一个包的产品,而不是它的内部目标。
  • 关系: 一个产品由一个或多个目标构建而成。例如,一个库产品 .library(name: "MyLib", targets: ["ModuleA", "ModuleB"]) 表示 MyLib 库是由 ModuleAModuleB 这两个目标编译链接而成的。

依赖解析过程:

swift package resolveswift build/swift run 被执行时,SPM 会进行依赖解析。

  1. 它读取你的 Package.swift 文件,获取你的直接依赖列表及其版本规则。
  2. 它获取每个直接依赖的 Package.swift 文件,找出它们的依赖列表。
  3. 这个过程会递归进行,构建一个完整的依赖关系图,包含所有直接和间接的依赖。
  4. SPM 会尝试为图中的每个包找到一个满足所有依赖规则的最新版本。如果同一个包被多个依赖以不同的版本规则引用,SPM 会尝试找到一个兼容所有规则的版本。如果找不到兼容版本,依赖解析将失败,并报告冲突。
  5. 成功的解析结果会写入 Package.resolved 文件。

理解这个过程有助于在遇到依赖冲突时进行排查。

资源文件 (Resources):

SPM 允许你将非代码资源文件(如图片、JSON、文本文件等)包含在包中。在 .target.library 声明中,可以使用 resources 参数:

swift
.target(
name: "MyTargetWithResources",
dependencies: [],
resources: [.process("Resources")] // 处理 Sources/MyTargetWithResources/Resources 目录下的所有文件
)

.process("Resources") 表示将 Sources/MyTargetWithResources/Resources/ 目录下的所有文件添加到构建产物中。这些资源在运行时可以通过 Bundle.module 来访问:

swift
// 假设 Resources 目录下有个 data.json 文件
let url = Bundle.module.url(forResource: "data", withExtension: "json")

这提供了一个标准化的方式来打包和访问与代码相关的资源。

9. 常用 SPM 命令详解

除了上面提到的,还有一些其他有用的命令:

  • swift package describe [--type json]:打印包的结构描述,包括产品、目标、依赖等信息。可以输出为 JSON 格式。
  • swift package edit <package-name> [--branch <branch-name>] [--path <path>]: 将一个依赖包置于“编辑模式”。SPM 会克隆该依赖的 Git 仓库到本地指定路径(默认为 SourcePackages/checkouts/),并让你的项目直接使用本地的代码,而不是缓存的版本。这在你需要修改依赖包代码或调试时非常有用。完成后使用 swift package unedit <package-name> 退出编辑模式。
  • swift package show-dependencies [--flatten]: 显示包的依赖树。--flatten 参数可以将依赖列表展平。
  • swift package completion-tool: 用于生成命令行补全脚本。

可以通过 swift package --help 查看所有可用命令及其选项。

10. 最佳实践与技巧

  • 提交 Package.resolved 文件: 这是保证构建可重复性的关键。
  • 使用语义版本控制: 对于你发布的包,遵循 SemVer 规范(MAJOR.MINOR.PATCH)。正确使用版本规则(尤其是 .from:.upToNextMajor)对于依赖你的开发者非常重要。
  • 模块化你的代码: 将大的功能拆分成多个目标,有助于提高代码的可维护性和重用性。
  • 编写测试: 为你的包编写全面的测试(在 Tests/ 目录下)是一个好习惯。swift test 命令可以方便地运行这些测试。
  • 理解 swift-tools-version 确保你的 Package.swift 中的版本与你使用的 Swift 工具链兼容。如果更新 Swift 版本,有时也需要更新 swift-tools-version
  • 本地路径依赖: 在开发阶段,你可以使用 .package(path: "../LocalPackage") 的方式依赖本地文件系统中的另一个包。这对于同时开发多个相互依赖的包非常有用。但在发布或提交代码时,通常不应包含这种本地路径依赖。

结论:掌握 SPM,提升开发效率

通过本教程,你应该对 Swift Package Manager 有了一个全面且详细的了解。从创建包、理解 Package.swift 文件,到添加依赖、在命令行和 Xcode 中使用 SPM,我们涵盖了快速入门所需的核心知识和实践步骤。

SPM 是 Swift 生态系统中一个强大而便捷的工具。掌握它,你将能够:

  • 更有效地组织和管理你的 Swift 代码。
  • 轻松地引入和使用第三方开源库。
  • 方便地创建和分发你自己的 Swift 代码模块。
  • 享受与 Xcode 无缝集成的开发体验。

依赖管理是现代软件开发中不可或缺的一环。拥抱 SPM,利用其自动化和标准化的能力,将极大地提升你的 Swift 开发效率和项目质量。

现在,就开始创建你的第一个 Swift 包,或者将 SPM 依赖添加到你的现有项目中吧!祝你开发顺利!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部