Swift 开发者的依赖管理:SPM 指南 – wiki基地


Swift 开发者的依赖管理:Swift Package Manager (SPM) 深度指南

在现代软件开发中,依赖管理是构建健壮、可维护应用的核心环节。开发者很少从零开始编写所有代码;相反,他们会利用大量的第三方库、框架和工具来加速开发、复用代码并集成现有功能。对于 Swift 开发者而言,有效管理这些外部依赖项是至关重要的。

长期以来,Swift 生态系统中的依赖管理主要由 CocoaPods 和 Carthage 这两个流行的第三方工具主导。它们各有优缺点,但也引入了额外的复杂性和潜在的兼容性问题。随着 Swift 语言和生态系统的成熟,苹果官方推出了 Swift Package Manager (SPM) 作为其原生的依赖管理解决方案,并将其深度集成到 Xcode 中。

本文将深入探讨 Swift Package Manager,详细介绍其工作原理、核心概念、如何在项目中使用 SPM 管理依赖项,以及如何创建和分发自己的 Swift 包。无论您是初学者还是经验丰富的 Swift 开发者,掌握 SPM 都将极大地提升您的开发效率和项目管理能力。

第一部分:认识 Swift Package Manager (SPM)

1.1 什么是 Swift Package Manager?

Swift Package Manager (SPM) 是一个用于管理 Swift 代码分发和构建的官方工具。它旨在提供一种标准化的方式来声明、获取、编译和链接 Swift 包(packages)及其依赖项。一个 Swift 包由 Swift 源代码文件、资源以及一个名为 Package.swift 的清单文件组成,该文件描述了包的名称、目标(targets)、产品(products)以及它所依赖的其他包。

SPM 是跨平台的,可以在 macOS、Linux、Windows 等支持 Swift 的平台上使用。它既可以通过命令行工具运行,也可以通过 Xcode 的图形界面进行操作,尤其在 Xcode 11 及以上版本中,SPM 集成得非常紧密。

1.2 为什么选择 SPM?

与 CocoaPods 或 Carthage 相比,SPM 作为官方解决方案具有多项显著优势:

  • 原生集成: SPM 被深度集成到 Xcode 中,提供了无缝的用户体验。您无需安装额外的第三方工具,可以直接在 Xcode 中添加、更新和管理依赖项。
  • Swift 原生: SPM 本身以及其包清单文件 Package.swift 都是用 Swift 编写的。这意味着 Swift 开发者无需学习新的 DSL(领域特定语言),可以直接用熟悉的 Swift 语法来定义包结构和依赖关系。
  • 去中心化: SPM 支持从任何 Git 仓库获取依赖项,无需依赖中心化的仓库或服务。您可以从 GitHub、GitLab、您自己的私有仓库等任何地方引用包。
  • 跨平台支持: SPM 不仅支持 Apple 平台(iOS, macOS, tvOS, watchOS),还支持 Linux 和其他支持 Swift 的平台。这对于开发跨平台工具、后端服务或命令行应用非常有利。
  • 模块化和结构化: SPM 强制要求遵循一定的包结构,有助于代码的模块化和组织。它清晰地定义了产品(可供其他包或应用使用的库或可执行文件)和目标(构建单元,如库目标、测试目标)。
  • 版本控制和可复现性: SPM 通过 Package.resolved 文件精确记录了每个依赖项在某个特定时间点解析到的具体版本,确保团队成员或不同构建环境能够使用完全相同的依赖图,提高了构建的可复现性。
  • 安全性: SPM 原生支持通过 Git Tag 进行版本管理,依赖于 Git 的安全性。虽然它不强制代码签名,但依赖于 Git 仓库的身份验证和访问控制。

尽管 CocoaPods 和 Carthage 依然有其应用场景(例如,某些库可能尚未支持 SPM,或者需要处理非 Swift/Objective-C 的依赖项),但对于新的 Swift 项目或主要依赖 Swift 包的现有项目,SPM 通常是首选的依赖管理方案。

1.3 SPM 与 CocoaPods / Carthage 的简要对比

特性 Swift Package Manager (SPM) CocoaPods Carthage
性质 Swift 官方,原生集成 第三方工具,Ruby Gem 第三方工具,Swift/Objective-C
集成方式 Xcode 深度集成,命令行 安装 Gem,生成 .xcworkspace 文件 安装 Tool,通过 carthage update 构建
配置文件 Package.swift (Swift) Podfile (Ruby DSL) Cartfile (自定义语法)
依赖来源 Git 仓库 (去中心化) 官方 Spec Repo 或私有 Repo (中心化/去中心化) Git 仓库 (去中心化)
构建产物 Swift Packages (库/可执行文件) Libraries/Frameworks Frameworks
项目集成 自动添加到 Xcode 项目 生成 .xcworkspace,需使用该文件 手动添加到 Xcode 项目中链接
平台支持 Apple 平台,Linux, Windows (Swift) Apple 平台 (主要是) Apple 平台
原生语言 主要 Swift Objective-C, Swift, 等 Objective-C, Swift, 等
锁文件 Package.resolved Podfile.lock Cartfile.resolved
二进制依赖 支持 支持 支持 (通过 Binaries)
资源文件 支持 支持 (通过 Resources) 支持

总的来说,SPM 的优势在于其原生性和集成度,简化了 Swift 项目的依赖管理流程,尤其是对于纯 Swift 或 Objective-C 兼容的库。

第二部分:SPM 的核心概念

理解 SPM 的核心概念是高效使用它的关键。

2.1 Package (包)

包是 SPM 的基本组织单元。一个包就是一个 Git 仓库(或者本地目录),包含:

  • Package.swift 文件: 包的清单文件,定义了包的元数据、产品、目标和依赖项。这是 SPM 工作的基础。
  • 源代码文件: 通常组织在特定的目录结构中(例如 SourcesTests)。
  • 资源文件: 例如图像、本地化字符串、配置文件等。

2.2 Package.swift (包清单文件)

Package.swift 是一个 Swift 文件,用于声明包的配置。它必须位于包的根目录。其基本结构如下:

“`swift
// swift-tools-version:5.9 // 指定SPM工具版本

import PackageDescription

let package = Package(
name: “MyAwesomeLibrary”, // 包的名称
platforms: [
.iOS(.v13), // 可选:指定支持的平台和最低版本
.macOS(.v10_15)
],
products: [
// 定义此包提供的产品(库或可执行文件)
.library(
name: “MyAwesomeLibrary”,
targets: [“MyAwesomeLibrary”]), // 指定哪些目标构成此产品
.executable(
name: “MyTool”,
targets: [“MyTool”])
],
dependencies: [
// 定义此包依赖的其他包
.package(url: “https://github.com/Alamofire/Alamofire.git”, .upToNextMajor(from: “5.8.1”)),
.package(url: “https://github.com/SnapKit/SnapKit.git”, .exact(“5.6.0”))
// 还可以引用本地包: .package(path: “../MyOtherLocalPackage”)
],
targets: [
// 定义包中的构建单元(目标)
.target(
name: “MyAwesomeLibrary”, // 库目标
dependencies: [ // 此目标依赖哪些其他目标或产品
.product(name: “Alamofire”, package: “Alamofire”) // 依赖来自Alamofire包的Alamofire产品
],
path: “Sources/MyAwesomeLibrary”, // 可选:指定源代码路径,默认是 “Sources/
resources: [ // 可选:包含的资源文件
.process(“Resources”) // 将 Resources 目录下的所有文件作为资源处理
]
),
.executableTarget( // 可执行文件目标
name: “MyTool”,
dependencies: [
.target(name: “MyAwesomeLibrary”) // 依赖本包内的库目标
],
path: “Sources/MyTool” // 默认是 “Sources/
),
.testTarget( // 测试目标
name: “MyAwesomeLibraryTests”,
dependencies: [“MyAwesomeLibrary”], // 依赖被测试的库目标
path: “Tests/MyAwesomeLibraryTests” // 默认是 “Tests/
)
]
)
“`

下面详细解释 Package.swift 中的主要元素:

  • // swift-tools-version:: 指定用于构建此包的 SPM 工具版本。这有助于确保包在不同 SPM 版本下的兼容性。
  • import PackageDescription: 导入 SPM 描述库,使得可以使用 PackageProductTarget 等类型。
  • let package = Package(...): 定义一个 Package 对象,这是清单文件的核心。
  • name:: 包的名称。通常与 Git 仓库名称或主产品名称相同。
  • platforms:: 可选。指定此包支持的最低平台版本。如果未指定,SPM 会使用默认值或根据依赖关系推断。
  • products:: 定义此包构建完成后可以提供给其他包或应用使用的“产品”。
    • .library(): 定义一个库产品。其他包或应用可以依赖这个库并导入其模块。可以指定库类型(静态 .static 或动态 .dynamic),默认通常由 SPM 决定或根据项目类型推断。
    • .executable(): 定义一个可执行文件产品。构建后会生成一个可以直接运行的二进制文件。
  • dependencies:: 定义此包所依赖的其他包(外部依赖项)。
    • .package(url: String, versionRequirement:): 通过 Git 仓库 URL 引用远程包。
    • .package(path: String): 引用本地文件系统中的包。这对于开发中的包或在同一个 workspace 中管理多个相关包非常有用。
    • versionRequirement: 定义依赖包的版本规则,这是依赖管理的关键。常见的规则包括:
      • .exact("1.2.3"): 精确匹配指定版本。
      • .upToNextMajor(from: "1.2.3"): 允许使用版本 >= 1.2.3 且 < 2.0.0 的任何版本。这是推荐的默认规则,因为它允许获取 bug 修复和次要更新,同时避免可能引入重大修改的主要版本。
      • .upToNextMinor(from: "1.2.3"): 允许使用版本 >= 1.2.3 且 < 1.3.0 的任何版本。更严格,只允许补丁和次要更新。
      • .range(from: "1.2.3", to: "1.5.0"): 允许使用版本 >= 1.2.3 且 < 1.5.0 的任何版本。
      • .branch("main"): 依赖某个分支的最新提交。不推荐在生产环境使用,因为分支是不断变化的,会导致构建不可复现。
      • .revision("abcdef1234567890"): 依赖某个特定的 Git 提交哈希。可以用于锁定到特定状态,但手动管理不便。
  • targets:: 定义包内的构建单元。目标是构建过程的实际执行者。
    • .target(name: String, dependencies: [...], path: String?, resources: [...], plugins: [...], settings: [...]): 定义一个标准目标,通常用于构建库。
    • .executableTarget(...): 定义一个可执行文件目标。
    • .testTarget(name: String, dependencies: [...], path: String?, resources: [...], settings: [...]): 定义一个测试目标,用于运行单元测试和 UI 测试。
    • dependencies: 定义 目标之间 的依赖关系,或者目标依赖于 外部包的产品。这里使用 .target(name:) 引用本包内的其他目标,使用 .product(name: package:) 引用外部包提供的产品。
    • path: 指定目标的源代码目录相对于 Package.swift 文件的路径。如果省略,SPM 会按照约定查找,例如 Sources/<targetName>Tests/<targetName>
    • resources: 定义目标需要包含的资源文件(如图像、JSON、本地化字符串)。可以使用 .process()(推荐,SPM 会根据平台优化资源)或 .copy()(直接复制文件)规则。路径相对于目标目录。
    • plugins: 定义目标使用的插件(如构建工具插件或命令插件)。
    • settings: 定义特定于目标的构建设置(如 Swift 编译标志、链接器标志)。

2.3 Products (产品)

产品是包的输出,是可以被其他包或应用依赖的部分。在 Package.swift 中通过 products: 数组定义。常见的有库(library)和可执行文件(executable)。一个包可以定义多个产品。当一个应用或另一个包添加此包作为依赖时,它实际上是依赖于此包声明的 产品

2.4 Targets (目标)

目标是包的构建单元,类似于 Xcode 项目中的 Target。每个目标对应源代码的一个逻辑分组。源代码文件必须属于某个目标。在 Package.swift 中通过 targets: 数组定义。SPM 主要有三种类型的目标:

  • 库目标 (.target): 编译生成一个模块,可以被其他目标或产品导入和使用。
  • 可执行文件目标 (.executableTarget): 编译生成一个可执行文件。通常包含 main.swift 或带有 @main 属性的类型作为入口点。
  • 测试目标 (.testTarget): 包含单元测试和 UI 测试代码,依赖于被测试的库目标或应用目标。SPM 可以自动发现并运行测试目标中的测试。

一个目标可以依赖于 本包内的其他目标外部包提供的产品。例如,一个库目标可能依赖于另一个库目标(内部模块化),或者依赖于一个外部 HTTP 请求库(如 Alamofire)。

2.5 Dependencies (依赖项)

依赖项是指一个包为了正常工作所需要的其他外部包。在 Package.swift 中通过 dependencies: 数组声明。依赖项通常通过 Git 仓库 URL 和版本要求来指定。SPM 会递归地解析所有依赖项的依赖项,构建完整的依赖图。

2.6 Resolving Dependencies (解析依赖项)

当您首次添加依赖项或更新依赖项版本时,SPM 会执行一个“解析”过程。它会检查您的 Package.swift 文件以及所有依赖项的 Package.swift 文件,构建一个完整的依赖图,并根据版本要求确定每个包应使用的具体版本。

这个解析过程的结果会被记录在一个名为 Package.resolved 的文件中。

2.7 Package.resolved (锁文件)

Package.resolved 文件(位于 .package 目录中,在 Xcode 项目中通常位于 .xcodeproj/project.xcworkspace/xcshareddata/swiftpm/.swiftpm/ 目录下)记录了 SPM 在最近一次解析时为每个依赖项选择的 精确版本、Git 提交哈希和校验和

重要性:

  • 可复现性: 确保团队中的每个成员或不同的构建服务器都能获得完全相同的依赖版本集合,避免“在我机器上好使”的问题。
  • 稳定性: 一旦生成,SPM 在后续构建时会优先使用 Package.resolved 文件中指定的版本,除非您 explicitly 要求更新。
  • 源代码控制: Package.resolved 文件应该被添加到您的版本控制系统(如 Git)中,与 Package.swift 文件一起提交。

当您更新 Package.swift 中的版本要求或添加新的依赖项时,SPM 会重新解析并更新 Package.resolved 文件。

第三部分:在 Xcode 中使用 SPM 管理依赖项

自 Xcode 11 起,SPM 被深度集成到 Xcode 中,成为管理项目依赖项的首选方式。

3.1 创建新的 Xcode 项目

创建一个新的 Xcode 项目(File > New > Project)。SPM 是项目模板的一部分,您无需执行额外步骤来启用它。

3.2 向现有项目添加依赖项

这是最常见的操作。假设您想在您的 iOS 应用中添加 Alamofire 这个流行的 HTTP 网络库:

  1. 打开项目: 在 Xcode 中打开您的项目(.xcodeproj.xcworkspace 文件)。
  2. 导航到添加包菜单: 选择菜单栏的 File > Add Packages...
  3. 输入包 URL: 在弹出的窗口中,搜索或粘贴您想要添加的 Swift 包的 Git 仓库 URL。例如,对于 Alamofire,输入 https://github.com/Alamofire/Alamofire.git。Xcode 会连接到仓库并获取包信息。
  4. 选择版本规则: 在搜索结果中,Xcode 会显示包的信息以及版本规则选项。通常有以下几种:
    • Up to Next Major Version: (例如 5.8.1 会选择 >= 5.8.1< 6.0.0) 推荐此选项
    • Up to Next Minor Version: (例如 5.8.1 会选择 >= 5.8.1< 5.9.0)
    • Exact Version: (例如 5.8.1) 锁定到精确版本,最严格。
    • Branch: (例如 main) 跟踪某个分支的最新提交,不推荐用于应用项目
    • Commit: 锁定到某个特定的提交哈希,不推荐用于日常开发
      选择适合您的版本规则(通常选择 Up to Next Major Version),并指定起始版本。
  5. 添加包: 点击 Add Package 按钮。
  6. 选择目标: Xcode 会下载并解析包。完成后,会显示包提供的产品(库、可执行文件等)以及您项目中的各个目标(应用目标、测试目标等)。您需要勾选您的哪些项目目标需要链接到此包的产品。例如,您的应用目标需要使用 Alamofire,就勾选应用目标旁边的 Alamofire 库产品。您的测试目标如果也需要使用它(比如在测试网络层时),也可以勾选。
  7. 完成: 点击 Add Package。Xcode 会将依赖项添加到您的项目中,下载代码,并更新 Package.swift(如果项目是使用 SPM 管理的项目)和 Package.resolved 文件。

现在,您就可以在您选择的目标的源代码中 import Alamofire 并开始使用它了。

3.3 更新依赖项

当您的依赖包发布了新版本,并且新版本符合您在 Package.swift 中指定的版本要求时,您可以更新它们:

  • 自动更新: 每次 Clean Build Folder (Product > Clean Build Folder) 或有时 Xcode 重新打开项目时,SPM 可能会检查并下载符合要求的新版本。
  • 手动更新:
    • 在项目导航器中选择您的项目文件(顶层蓝色的文件)。
    • 在项目编辑器的 Package Dependencies 标签页中,您可以看到所有通过 SPM 添加的依赖项。
    • 点击底部的 Update to Latest Versions 按钮。SPM 会重新解析依赖图,下载符合版本要求的最新版本,并更新 Package.resolved 文件。

如果您想更新到一个不符合当前版本要求的新主要版本(例如,从 5.x 更新到 6.x),您需要手动修改 Package.swift 文件中的版本要求,或者在 Xcode 的 Package Dependencies 标签页中选中该依赖项,然后点击 Edit 按钮来修改版本规则。修改后,再次执行更新操作。

3.4 删除依赖项

要从您的项目中删除一个 SPM 依赖项:

  1. 在项目导航器中选择您的项目文件。
  2. 在项目编辑器的 Package Dependencies 标签页中,选中您想要删除的依赖项。
  3. 点击下方的减号 (-) 按钮。
  4. 确认删除。Xcode 会从项目中移除依赖项,并更新 Package.swiftPackage.resolved 文件。

3.5 SPM 在 Xcode 中的位置

通过 Xcode 添加的 SPM 依赖项的文件通常存储在项目根目录下的 .package 隐藏目录中。您可以在 Xcode 的项目导航器中展开 Swift Package Dependencies 部分来查看所有已添加的包及其内容(但通常不建议直接修改这些文件)。

Package.resolved 文件通常位于 .xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

第四部分:使用命令行工具管理 SPM

除了 Xcode,您还可以完全使用命令行工具 swift package 来管理 Swift 包。这对于自动化构建、CI/CD 环境以及开发 Swift 包本身非常有用。

4.1 创建一个新的 Swift 包

要创建一个新的 Swift 包(而不是 Xcode 项目中的依赖项):

“`bash

进入您想创建包的目录

cd /path/to/your/dev/folder

创建一个库包

swift package init –type library

或者创建一个可执行文件包

swift package init –type executable
“`

这会在当前目录下创建一个新的文件夹(以您的命令执行目录命名),并在其中生成一个基本的包结构:

  • Package.swift
  • .gitignore
  • Sources/YourPackageName/YourPackageName.swift (对于库) 或 Sources/YourPackageName/main.swift (对于可执行文件)
  • Tests/YourPackageNameTests/YourPackageNameTests.swift

4.2 添加依赖项 (命令行)

如果您是开发一个 Swift 包本身,而不是在 Xcode 项目中使用依赖,您需要手动编辑 Package.swift 文件来添加依赖项。例如,向上面创建的库包添加一个依赖:

编辑 Package.swift 文件,在 dependencies 数组中添加新的依赖项,例如:

“`swift
// … (前面部分省略)

dependencies: [
.package(url: “https://github.com/Alamofire/Alamofire.git”, .upToNextMajor(from: “5.8.1”))
],

targets: [
.target(
name: “YourPackageName”,
dependencies: [
.product(name: “Alamofire”, package: “Alamofire”) // 不要忘记在目标中引用产品
]),
.testTarget(
name: “YourPackageNameTests”,
dependencies: [“YourPackageName”]),
]

// … (后面部分省略)
“`

保存 Package.swift 文件后,运行以下命令来解析和下载依赖项:

bash
swift package resolve

或者,您也可以运行构建命令,它会自动先解析依赖:

bash
swift build

这将下载依赖包到 .build/checkouts 目录,并生成 Package.resolved 文件。

4.3 构建包

在包的根目录(包含 Package.swift 的目录)下,运行构建命令:

bash
swift build

这会编译包中的所有目标。构建产物(库文件 .dylib.a,可执行文件)会生成在 .build/debug.build/release 目录下(取决于您是否使用 --configuration release 标志)。

4.4 运行可执行包

如果您的包包含可执行文件目标,您可以使用 swift run 命令直接运行它:

bash
swift run YourExecutableTargetName

这会先构建可执行文件(如果需要),然后运行它。

4.5 运行测试

使用 swift test 命令来运行包中的测试目标:

bash
swift test

这会构建测试目标并运行其中的所有测试用例。

4.6 其他常用命令行命令

  • swift package update: 更新依赖项到符合版本要求的最新版本,并更新 Package.resolved
  • swift package clean: 清理构建产物和缓存的依赖项。在遇到奇怪的构建问题时很有用。
  • swift package show-dependencies: 显示包的依赖图。
  • swift package generate-xcodeproj: (已不推荐,Xcode 11+ 直接打开包目录即可)生成一个临时的 Xcode 项目文件来编辑和构建包。

第五部分:创建和分发自己的 Swift 包

如果您开发了一个有用的 Swift 代码库,并希望与他人分享或在多个项目中使用,您可以将其打包成一个 Swift 包。

5.1 包的结构

遵循标准的 SPM 包结构是一个好的开始:

MyAwesomePackage/
├── Package.swift
├── Sources/
│ └── MyAwesomePackage/
│ └── MyAwesomePackage.swift
├── Tests/
│ └── MyAwesomePackageTests/
│ └── MyAwesomePackageTests.swift
└── README.md

  • Sources 目录下包含库或可执行文件的源代码。每个子目录通常对应一个目标,其名称应与 Package.swift 中定义的目标名称匹配。
  • Tests 目录下包含测试代码,结构类似。
  • 其他文件如 README.mdLICENSE 等位于根目录。

5.2 编写 Package.swift

按照前面介绍的格式,编写您的 Package.swift 文件。定义包的名称、产品、依赖项(如果您有的话)和目标。确保 products 中导出了您希望其他用户可以使用的库或可执行文件,并在 targets 中正确设置了源代码路径和目标依赖。

5.3 编写代码和测试

Sources 目录下编写您的库或可执行文件的代码。在 Tests 目录下为您的代码编写单元测试。确保所有代码都属于某个目标。

5.4 版本控制和 Git 仓库

将您的代码放在 Git 仓库中。SPM 依赖于 Git 来获取远程包。

5.5 发布版本 (打 Tag)

为了让其他用户能够稳定地依赖您的包,强烈建议使用 Git Tag 来标记发布的版本。遵循 语义化版本控制 (Semantic Versioning) 规范(MAJOR.MINOR.PATCH,例如 1.0.0, 1.1.0, 2.0.0)。

  • MAJOR 版本:当你做了不兼容的 API 修改。
  • MINOR 版本:当你做了向下兼容的功能性新增。
  • PATCH 版本:当你做了向下兼容的问题修正。

使用以下命令打 Tag 并推送到远程仓库:

bash
git tag 1.0.0
git push origin 1.0.0

当用户在 Package.swift 中依赖您的包时,他们会指定一个版本要求(例如 .upToNextMajor(from: "1.0.0")),SPM 会查找符合要求的 Git Tag。

5.6 文档

为您的包编写清晰的文档(通常在 README.md 中),说明其用途、如何集成(尽管 SPM 集成很简单)、API 使用方法等。

5.7 分发

将您的 Git 仓库托管到公共平台(如 GitHub、GitLab)或您自己的私有服务器。其他开发者就可以通过您的仓库 URL 在他们的项目中使用 SPM 引用您的包了。无需向任何中心化的仓库注册您的包。

第六部分:SPM 的高级特性

SPM 不仅限于简单的库和依赖管理,还支持一些更高级的特性:

6.1 二进制目标 (Binary Targets)

如果您想分发一个闭源的或预编译好的库(如 .xcframework 文件),可以使用二进制目标。

swift
// 在 Package.swift 的 targets 数组中
.binaryTarget(
name: "MyClosedSourceLibrary",
path: "Frameworks/MyClosedSourceLibrary.xcframework" // 指定 .xcframework 文件的路径
),
// 或者从远程 URL 获取
.binaryTarget(
name: "AnotherBinaryLibrary",
url: "https://example.com/binary/AnotherBinaryLibrary.xcframework.zip",
checksum: "a1b2c3d4e5f6..." // 文件的 SHA256 校验和,用于验证下载
)

在依赖二进制目标的 .target 中,像引用普通目标一样引用它:

swift
.target(
name: "MyApp",
dependencies: [
.target(name: "MyClosedSourceLibrary")
]
)

注意: 二进制目标主要用于分发预编译的特定平台框架(如 iOS, macOS),因此它们通常与平台限制一起使用。从远程 URL 获取二进制目标时,提供校验和是强制性的,以确保文件的完整性和安全性。

6.2 资源文件 (Resources)

SPM 支持在包中包含资源文件,如图片、音频、本地化字符串、配置文件等。

在目标的定义中,使用 resources 参数:

swift
.target(
name: "MyLibrary",
dependencies: [],
resources: [
.process("Resources/Images"), // 处理 Images 目录下的所有文件
.process("Resources/Data.json"), // 处理单个文件
.copy("Resources/Config.plist") // 直接复制文件,不进行平台优化处理
]
)

  • .process() 是推荐的方式,SPM 会根据目标平台对资源进行优化处理(例如,为不同设备缩放图像)。
  • .copy() 会直接将文件复制到目标目录中,适用于不需要特殊处理的文件。

在代码中访问这些资源,需要使用正确的 Bundle:

swift
// 在 MyLibrary 模块中访问资源
let bundle = Bundle.module // Bundle.module 是 SPM 生成的,指向当前包的 Bundle
if let image = NSImage(named: "my_image", in: bundle) {
// 使用图片
}
if let url = bundle.url(forResource: "Data", withExtension: "json") {
// 读取数据
}

Bundle.module 是 SPM 的一个便利特性,它会自动创建一个代表当前 Swift 包的 Bundle。

6.3 平台和架构条件 (Platform and Architecture Conditions)

有时候,包中的某些目标或依赖项可能只适用于特定的平台或架构。可以使用条件来指定这些约束。

swift
dependencies: [
// 只在 iOS 或 macOS 平台上依赖 MyAwesomeLibrary
.package(url: "...", .upToNextMajor(from: "1.0.0"), condition: .when(platforms: [.iOS, .macOS])),
// 只在 Linux 平台上依赖 AnotherLibrary
.package(url: "...", .upToNextMajor(from: "1.0.0"), condition: .when(platforms: [.linux]))
],
targets: [
.target(
name: "MyCore",
dependencies: [
// 只在模拟器架构上依赖 DebuggingHelper 库产品
.product(name: "DebuggingHelper", package: "...", condition: .when(architectures: [.x86_64, .arm64], platforms: [.iOS, .macOS]))
]
),
// 只在非模拟器设备架构上包含 DeviceSpecificTarget
.target(
name: "DeviceSpecificTarget",
dependencies: [],
path: "Sources/DeviceSpecificTarget",
swiftSettings: [.define("IS_DEVICE", .when(platforms: [.iOS], configuration: .release, architectures: [.arm64]))] // 可以在 Swift 编译设置中定义条件宏
, condition: .when(platforms: [.iOS], architectures: [.arm64]))
]

condition 参数使用 .when 枚举,可以指定 platformsarchitectures 的数组。这使得同一个 Package.swift 文件可以支持跨平台开发,并根据不同的构建环境包含或排除特定的代码和依赖。

6.4 插件 (Plugins)

SPM 5.6 引入了插件 API,允许开发者创建自定义的构建工具插件或命令插件。构建工具插件可以在编译过程的前后执行任务(如代码生成、Linting),命令插件可以添加新的 swift package 子命令。这是一个更高级的主题,通常用于扩展 SPM 的功能或集成其他开发工具。

第七部分:SPM 的最佳实践

  • 使用语义化版本控制 (SemVer): 如果您是包的作者,严格遵循 SemVer 规范发布版本是至关重要的,这有助于其他依赖您的开发者更好地管理更新和兼容性。
  • 使用 .upToNextMajor 版本规则: 对于依赖项,.upToNextMajor 是一个很好的默认选择。它允许您获取 bug 修复和新功能,同时避免引入重大 API 更改的风险。
  • Package.resolved 添加到版本控制: 这确保了团队成员和 CI/CD 环境使用相同的依赖版本。
  • 保持 Package.swift 清晰: 合理组织您的目标和产品,使用明确的命名。
  • 编写测试: 为您的包编写全面的测试。SPM 可以很好地集成和运行测试。
  • 提供清晰的文档:README.md 或其他地方提供包的说明、用法和示例。
  • 利用 .package(path:) 进行本地开发: 在同时开发多个相互依赖的本地包时,使用本地路径依赖可以极大地简化开发和测试流程。
  • 谨慎使用分支或提交依赖: 它们会影响构建的可复现性。只在极少数情况下(例如,测试某个新功能或 bug 修复尚未打 tag)考虑使用,并在稳定版本发布后尽快切换回版本依赖。
  • 理解产品和目标的区别: 产品是包的 公共接口,目标是 实现细节。只有产品可以被外部依赖。

第八部分:常见问题与故障排除

  • 依赖解析失败 (Dependency resolution failed):
    • 版本冲突: 您的不同依赖项可能依赖于同一个库的不同且不兼容的版本。检查错误消息,看看是哪个包引发了冲突。有时需要调整您的依赖版本要求,或者等待某个库发布兼容版本。
    • 网络问题: 无法访问 Git 仓库。检查网络连接。
    • 认证问题: 如果依赖私有仓库,确保您的 Git 客户端或 Xcode 具有访问权限。
  • 找不到模块 (No such module ‘ModuleName’):
    • 未正确链接产品: 在 Xcode 中添加依赖项时,确保您勾选了需要在您的目标中使用的产品。
    • 拼写错误: 检查 import 语句中的模块名是否正确。模块名通常是产品名称。
    • Xcode 索引问题: 有时 Xcode 索引会损坏。尝试 Clean Build Folder (Product > Clean Build Folder),然后重新 Build。如果不行,关闭 Xcode,删除 Derived Data 目录,再重新打开项目。
  • 资源文件找不到: 确保您在 Package.swift 中使用 resources 参数包含了资源,并在代码中使用 Bundle.module 来加载这些资源。资源路径需要相对于目标目录。
  • 命令行与 Xcode 行为不一致: 确保您在命令行和 Xcode 中使用的是相同版本的 Swift 和 SPM 工具链。Xcode 的 SPM 集成可能会自动处理一些细节(如 resolve),而命令行需要手动执行命令。

结论

Swift Package Manager 已经成为 Swift 生态系统中官方且首选的依赖管理工具。其深度集成到 Xcode、原生 Swift 语法、去中心化特性和强大的命令行支持,极大地简化了 Swift 项目的依赖管理和包分发流程。

通过理解 SPM 的核心概念——包、清单文件 (Package.swift)、产品、目标和依赖项——以及掌握在 Xcode 和命令行中的使用方法,Swift 开发者可以更高效地利用外部代码、更好地组织自己的代码,并为 Swift 社区贡献自己的库。

尽管 SPM 在不断发展完善中,目前它已经足够成熟,能够满足绝大多数 Swift 项目的依赖管理需求。拥抱 SPM,让您的 Swift 开发体验更加顺畅和现代化。


发表评论

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

滚动至顶部