SPM:什么是它,Swift Package Manager 和 Package.swift

作者: IT Sectr 发布日期: 2026-02-13 阅读时间: 11 分钟

SPM(Swift Package Manager)—— Swift 生态系统内置的包管理器,由 Apple 开发,用于自动化连接、构建和更新第三方库。SPM 从 Swift 3.0(2016年)起就成为 Swift 编译器的一部分,无需单独安装。与 CocoaPods 和 Carthage 不同,SPM 直接与编译器和 Xcode 集成,使其成为现代 Swift 项目中依赖管理的标准工具。本文将解析 Package.swift 的结构、SPM 命令、编写自己的包以及从其他管理器迁移。

要点

  • SPM(Swift Package Manager)—— Swift 编译器内置的包管理器,无需单独安装;支持 iOS、macOS、Linux 和服务端平台。
  • Package.swift —— 清单文件,以声明式格式描述包名称、平台、依赖关系和目标模块(targets)。
  • SPM 根据语义化版本(SemVer)解决依赖,缓存源代码并并行构建包以加速。
  • 命令:swift package init(创建包)、swift package update(更新依赖)、swift build(构建)、swift test(运行测试)。
  • 从 CocoaPods/Carthage 迁移到 SPM 通过 Xcode 完成:File → Add Package Dependency,之后删除 podfile 和 Cartfile。

什么是 SPM?

SPM(Swift Package Manager) —— Swift 语言的官方包管理器,内置于 swiftc 编译器和 Xcode 开发环境中。它允许开发者连接第三方库、管理其版本并发布自己的包。SPM 首次出现在 Swift 3.0(2016年9月)中作为命令行工具,从 Xcode 11(2019年)开始与图形界面完全集成——现在可以通过 File → Add Packages 菜单添加依赖。

SPM 自动从 Git 仓库下载依赖的源代码,与主项目并行构建,并缓存结果以便后续构建更快。与 CocoaPods 不同,SPM 不会生成独立的工作区(xcworkspace)——依赖成为主 Xcode 项目的一部分。根据 Swift.org Developer Survey(2024)调查,67% 的 iOS 开发者使用 SPM,使其成为 Swift 生态中最流行的依赖管理工具。

SPM 支持三个平台:Apple(iOS、macOS、tvOS、watchOS、visionOS)、Linux(Ubuntu、CentOS、Amazon Linux)和服务端 Swift(Vapor、Kitura)。在 Linux 上,SPM 完全通过命令行工作,无需 Xcode。

Swift Package Manager 的工作原理

SPM 围绕三个核心概念构建:(packages)、产品(products)和目标(targets)。包是一个带有 Package.swift 清单的 Git 仓库。产品是构建结果(库或可执行文件)。目标是包内的模块,编译为构建单元。

当开发者在 Package.swift 中添加依赖时,SPM 执行以下步骤:

  1. 克隆 —— SPM 从指定 URL 下载依赖的 Git 仓库。
  2. 版本解析 —— 分析 SemVer 标签(如 2.1.3)并在指定范围内选择合适版本。
  3. 传递性解析 —— 检查依赖的依赖并构建无版本冲突的图谱。
  4. 缓存 —— 将下载的源代码保存在 ~Library/Caches/org.swift.swiftpm/ 中。
  5. 编译 —— 使用主项目的标志构建包的所有目标。

Package.resolved 文件固定所有依赖的确切版本,以便开发团队使用相同的库集。此文件应添加到版本控制系统(git)中。

SPM 相比同类工具的主要优势——没有集中式注册中心。包可以位于任何公共 Git 仓库中:GitHub、GitLab、Bitbucket,以及公司自己的 Git 服务器上。从 Swift 5.2 开始,SPM 支持二进制依赖(binary targets)——通过 XCFramework 分发而不提供源代码的闭源库。

Package.swift —— 项目清单

Package.swift —— 是一个 Swift 文件,描述包的结构及其依赖关系。该文件用 Swift 本身编写(不是 JSON,不是 YAML),允许在清单中使用条件逻辑、计算常量和函数。

Package.swift 的基本结构:

swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [
        .iOS(.v16),
        .macOS(.v13)
    ],
    products: [
        .library(
            name: "MyLibrary",
            targets: ["MyLibrary"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git",
                 from: "5.9.0"),
        .package(url: "https://github.com/onevcat/Kingfisher.git",
                 from: "7.12.0"),
    ],
    targets: [
        .target(
            name: "MyLibrary",
            dependencies: [
                "Alamofire",
                "Kingfisher"
            ]
        ),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary"]
        ),
    ]
)

我们来分析关键元素:

  • // swift-tools-version: 5.9 —— 指示 SPM 版本的指令;清单的可用语法取决于此。
  • name —— 包名称,在 Xcode 中显示并用于依赖引用。
  • platforms —— 平台的最低版本;SPM 不允许在较旧的 OS 版本上构建包。
  • products —— 包“导出”的内容:库(.library)或可执行文件(.executable)。
  • dependencies —— 外部包列表,包含 URL 和版本;支持 from:exact:branch:revision:
  • targets —— 构建目标;每个目标包含依赖列表、资源和来自相应目录(Sources/TargetName/)的 swift 文件。

指定确切版本、分支和提交的示例:

swift
dependencies: [
    .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git",
             exact: "1.17.3"),
    .package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",
             branch: "main"),
    .package(url: "https://github.com/apple/swift-log.git",
             revision: "e5c6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4"),
]

从 Swift 5.9 开始,Package.swift 添加了对 static/frameworklinkerSettings 的支持,允许更精确地配置静态和动态库的链接。

SPM 主要命令

Swift Package Manager 提供了一套通过终端工作的命令。命令从包的根目录(Package.swift 所在位置)运行。

bash
# 创建带有库的新包
swift package init --type library

# 创建可执行包(控制台应用程序)
swift package init --type executable

# 构建项目
swift build

# 以 release 配置构建
swift build -c release

# 运行测试
swift test

# 运行特定测试
swift test --filter "MyLibraryTests/testExample"

# 下载并解析依赖
swift package resolve

# 将依赖更新到最新可用版本
swift package update

# 显示依赖图
swift package show-dependencies

# 清除构建缓存
swift package clean

# 生成 Xcode 项目(Xcode 11 之前)
swift package generate-xcodeproj

在 Xcode 内部工作时,大多数这些命令会自动执行:依赖在打开项目时解析,构建通过 ⌘B 启动,测试通过 ⌘U 启动。然而,了解终端命令对于 CI/CD 管道(GitHub Actions、GitLab CI、Jenkins)是必需的,因为在这些环境中 Xcode 不可用。

swift package resolve 命令创建或更新 Package.resolved 文件。此文件固定所有依赖(包括传递依赖)的确切版本,并应添加到 git 中。建议在每个新功能分支之前运行 swift package update,以便使用最新的库版本。

创建自己的包

创建自己的 SPM 包对于在多模块项目中封装业务逻辑以及发布开源库非常有用。让我们逐步了解过程。

第 1 步:初始化

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

第 2 步:目录结构

swift package init 命令创建以下结构:

text
MyNetworkKit/
├── Package.swift
├── README.md
├── Sources/
│   └── MyNetworkKit/
│       └── MyNetworkKit.swift
└── Tests/
    └── MyNetworkKitTests/
        └── MyNetworkKitTests.swift

SPM 自动扫描 Sources/Tests/ 目录:Sources 内的每个子目录对应一个目标(target)。

第 3 步:编辑 Package.swift

让我们添加依赖并配置目标平台:

swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyNetworkKit",
    platforms: [
        .iOS(.v15),
        .macOS(.v12)
    ],
    products: [
        .library(
            name: "MyNetworkKit",
            targets: ["MyNetworkKit"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git",
                 from: "5.9.0"),
    ],
    targets: [
        .target(
            name: "MyNetworkKit",
            dependencies: ["Alamofire"]
        ),
        .testTarget(
            name: "MyNetworkKitTests",
            dependencies: ["MyNetworkKit"]
        ),
    ]
)

第 4 步:编写代码

swift
// Sources/MyNetworkKit/MyNetworkKit.swift
import Foundation
import Alamofire

public struct NetworkClient {
    private let session: Session

    public init() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        self.session = Session(configuration: configuration)
    }

    public func fetchData(from url: String) async throws -> Data {
        let response = try await session.request(url).serializingData().value
        return response
    }
}

第 5 步:发布

将包推送到 Git 仓库并创建 SemVer 标签:

bash
git init
git add .
git commit -m "Initial commit: MyNetworkKit"
git remote add origin https://github.com/username/MyNetworkKit.git
git push -u origin main
git tag 1.0.0
git push --tags

之后,任何开发者都可以通过 .package(url: "https://github.com/username/MyNetworkKit.git", from: "1.0.0") 连接您的包。

SPM 使用示例

示例 1:连接 Alamofire 进行网络请求

Alamofire —— Swift 最流行的 HTTP 客户端。让我们通过 SPM 添加它并执行 GET 请求。

swift
import Alamofire

func fetchUsers() {
    AF.request("https://jsonplaceholder.typicode.com/users")
        .validate()
        .responseDecodable(of: [User].self) { response in
            switch response.result {
            case .success(let users):
                print("收到 \(users.count) 个用户")
            case .failure(let error):
                print("错误:\(error.localizedDescription)")
            }
        }
}

示例 2:Swinject —— 依赖注入

Swinject 库为 Swift 提供 DI 容器。通过 .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0") 连接。

swift
import Swinject

let container = Container()
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
container.register(DataRepositoryProtocol.self) { r in
    DataRepository(networkService: r.resolve(NetworkServiceProtocol.self)!)
}

let repository = container.resolve(DataRepositoryProtocol.self)
repository?.loadData()

示例 3:Swift-log 用于结构化日志记录

Apple 的 swift-log 包 —— 统一日志记录 API,支持多种后端(OSLog、控制台、文件)。

swift
import Logging

var logger = Logger(label: "com.myapp.network")
logger.logLevel = .debug

logger.info("网络请求已开始", metadata: [
    "url": "\(requestURL)",
    "method": "GET"
])

logger.warning("响应时间超过 2 秒")
logger.error("连接错误:无互联网")

这三个示例涵盖了 SPM 的典型使用场景:HTTP 客户端、DI 容器和系统基础设施。库的选择并非偶然——Alamofire、Swinject 和 swift-log 位列 GitHub 上最受欢迎的 Swift 包前 20 名。

从 CocoaPods 和 Carthage 迁移

如果项目使用 CocoaPods 或 Carthage,迁移到 SPM 需要几个步骤。该过程是安全的:SPM 依赖可以与 CocoaPods 和 Carthage 在同一项目中共存,从而实现逐步迁移。

CocoaPods → SPM

  1. 在 Xcode 中:File → Add Package Dependency,输入包的 URL。
  2. 选择版本并将包添加到所需的目标(targets)。
  3. 通过 SPM 添加所有依赖后,从 Podfile 中删除行。
  4. 删除 .xcworkspace,打开 .xcodeproj 并执行 Clean Build Folder。

Carthage → SPM

  1. 通过 Xcode File → Add Package Dependency 添加包。
  2. 从 Cartfile 中删除依赖。
  3. 从 Build Phases 中删除 Carthage 构建脚本。
  4. 清除缓存:在终端中执行 rm -rf Carthage/

截至 2025 年,SPM 支持绝大多数流行的 Swift 库。例外是一些没有模块映射(modulemap)的 ObjC 框架。如果库尚不支持 SPM——请检查其 README 中的 Installation 部分;大多数作者已在最新版本中添加了 SPM 支持。

常见问题

SPM 与 CocoaPods 和 Carthage 有何不同?

SPM 内置于 Swift 编译器和 Xcode 中,无需通过 gem 或 Homebrew 安装。CocoaPods 使用集中式 Specs 注册表并生成独立的工作区。Carthage 通过框架工作,不与项目集成。SPM 是唯一在编译器级别集成的管理器:依赖被解析、缓存并与主代码并行构建。

SPM 可以用于 Objective-C 项目吗?

可以,SPM 支持 Swift + Objective-C 混合项目。SPM 包中的 ObjC 文件在 modulemap 正确的情况下会自动归入 Umbrella Header。但是,SPM 不支持没有模块映射的静态 ObjC 库。建议仅在 ObjC 库提供 modulemap 或由纯 C 编写时通过 SPM 连接它们。

SPM 如何解决版本冲突?

SPM 使用语义化版本控制(SemVer)。如果包 A 需要 Alamofire 5.8+,包 B 需要 Alamofire 5.9+,SPM 将选择满足两者的 5.9.x 版本。如果冲突无法解决(一个包需要 5.x,另一个需要 6.x),SPM 将报告错误。在这种情况下,您需要更新其中一个包或将依赖更改为与两者兼容的版本。

下载的 SPM 包存储在哪里?

在 macOS 上:~Library/Caches/org.swift.swiftpm/~/Library/Developer/Xcode/DerivedData/。在 Linux 上:~cache/swiftpm/。构建时,SPM 会缓存源代码和编译的对象文件。要完全清除缓存,请执行 swift package reset — 此命令会删除当前项目的依赖缓存和 DerivedData。

SPM 是否支持闭源(专有)库?

支持,从 Swift 5.2 开始,SPM 支持二进制目标(binary targets)。闭源库作为 XCFramework 提供,在 Package.swift 中指定 .xcframework 的路径。源代码不会公开。二进制目标通过 .binaryTarget(name: "PrivateSDK", path: "Sources/PrivateSDK.xcframework") 指定。这允许连接商业 SDK 而不违反许可协议。

总结

  • SPM(Swift Package Manager)—— 内置的 Swift 包管理器,无需单独安装,与 Xcode 和编译器集成。
  • Package.swift —— 用 Swift 语言编写的声明式清单,描述包名称、平台、依赖、产品和构建目标。
  • SPM 使用 Git 仓库作为包源,根据 SemVer 解析版本,缓存源代码以加速后续构建。
  • 主要命令:swift package init(创建包)、swift build(构建)、swift test(测试)、swift package update(更新依赖)。
  • 自己的包通过 swift package init 创建,发布到 Git,通过带有 SemVer 标签的 URL 供其他项目使用。
  • 从 CocoaPods/Carthage 迁移到 SPM 是安全的:依赖可以共存,迁移通过 Xcode 中的 File → Add Package Dependency 完成。
  • SPM —— Swift 生态系统中的依赖管理标准,被 67% 的 iOS 开发者使用(Swift.org Developer Survey,2024)。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读