1.1 快速上手
原文链接: https://docs.swift.org/latest/documentation/packagemanagerdocs/gettingstarted/
1.1 快速上手
学习创建和使用 Swift 包。
概述
为了更完整地展示 Swift Package Manager 能做什么,下面的例子由三个相互依赖、可供你自行探索的包组成:
本指南会展示如何创建一个库、让它依赖另一个库,如何使用包管理器构建和测试代码,以及如何发布你自己的包。
创建库包
这个例子从使用一个已有包开始:它表示一副标准 52 张扑克牌中的一张。这个包 PlayingCard 通过 git 提供,其中的库(PlayingCard)是本指南使用并加以扩展的基础。
这个例子要创建的库 DeckOfPlayingCards 提供一个表示牌组的类型,以及对该牌组的常见操作,包括洗牌、计数和发牌。要新建一个库,先创建一个空目录,在其中运行 swift package init 命令来初始化新包:
1
2
3
| mkdir DeckOfPlayingCards
cd DeckOfPlayingCards
swift package init
|
包管理器创建的默认模板是一个库,你可以用 --type 参数控制。包名默认取你创建目录的名字,也可以通过 swift package init 命令的 --name 参数覆盖。关于该命令选项的完整细节,参见 swift package init 文档。
模板会在目录中生成一套符合 Swift 包默认约定的文件结构:
1
2
3
4
5
6
7
8
9
| DeckOfPlayingCards
├── .gitignore
├── Package.swift
├── Sources
│ └── DeckOfPlayingCards
│ └── DeckOfPlayingCards.swift
└── Tests
└── DeckOfPlayingCardsTests
└── DeckOfPlayingCardsTests.swift
|
模板在 Sources/DeckOfPlayingcards 提供了一个用于承载单个模块的目录(该模块作为库暴露出去),并提供一个与之对应的测试目录。默认的包结构提供一个由单个目标构成的库,库名与目标名都与包名相同(DeckOfPlayingCards),另外还有一个测试目标,你可以在开发代码的过程中把测试加进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| let package = Package(
name: "DeckOfPlayingCards",
products: [
// 产品定义包产出的可执行文件和库,使它们对其他包可见。
.library(
name: "DeckOfPlayingCards",
targets: ["DeckOfPlayingCards"]
),
],
targets: [
// 目标是包的基本构建单元,定义一个模块或测试套件。
// 目标可以依赖本包内的其他目标,以及依赖所提供的产品。
.target(
name: "DeckOfPlayingCards"
),
.testTarget(
name: "DeckOfPlayingCardsTests",
dependencies: ["DeckOfPlayingCards"]
),
]
)
|
添加依赖
要让这个例子使用提供 PlayingCard 的库,就需要添加对它的依赖:使用 add-dependency 命令,并给出该包的托管位置。
1
| swift package add-dependency https://github.com/apple/example-package-playingcard --from 3.0.0
|
包中的每个依赖都要指定源 URL 和版本要求。源 URL 是当前用户可访问、且能解析到某个 Git 仓库的 URL。包管理器用这些遵循语义化版本(SemVer)约定的版本要求来决定检出并使用哪个 Git 标签来构建该依赖。
上面的例子使用参数 --from 3.0.0 来说明该依赖的版本要求:from 把选定的依赖限制在最低 3.0.0,最高可到该 Git 仓库中可用的最高次版本和补丁版本。包管理器用标签(并将其解释为语义化版本)来确定可用的版本。该命令更新了 Package.swift 清单文件,加入 dependencies 一节:
1
2
3
4
5
6
7
8
9
10
11
12
| let package = Package(
name: "DeckOfPlayingCards",
products: [
...
],
dependencies: [
.package(url: "https://github.com/apple/example-package-playingcard", from: "3.0.0"),
],
targets: [
...
]
)
|
添加依赖只是让该依赖对包可用,默认并不会把它包含进包内的目标中。例如,如果你尝试用 swift build 构建这个包,构建会成功,但会给出警告:
1
| warning: 'deckofplayingcards': dependency 'example-package-playingcard' is not used by any target
|
如果你试图在源代码中使用这个库,例如写 import PlayingCard,编译器会报告 No such module 'PlayingCard'。
运行 swift build 命令时,包管理器会下载全部依赖、编译它们,并根据 Package.swift 清单把它们链接到包模块上。你可以在项目根目录的 .build/checkouts 目录中看到下载下来的源码,在项目根目录的 .build 目录中看到中间构建产物。
你还需要在想要使用该库的目标上加入这一依赖。使用 add-target-dependency 命令可以把该依赖加到本包中的目标上。
1
| swift package add-target-dependency PlayingCard DeckOfPlayingCards --package example-package-playingcard
|
这样 DeckOfPlayingCards 就能通过 import 语句访问其依赖模块的公开成员。上面的命令会更新 Package.swift 清单,使 DeckOfPlayingCards 目标引用该依赖:
1
2
3
4
5
6
| .target(
name: "DeckOfPlayingCards",
dependencies: [
.target(name: "PlayingCard"),
]
),
|
完成这一修改后,再运行 swift build,包就能无警告地通过编译。
实现这个库
模板为你的包源代码提供了一个空文件。清空其内容,加上 import PlayingCard,然后写你的实现。下面的代码给出一个示例实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| import PlayingCard
/// 用于洗牌和发牌的扑克牌组模型。
///
/// 一副扑克牌由 52 张牌组成,分属四个花色:黑桃、红桃、方块和梅花。
/// 点数有 13 种:从 2 到 10,然后是 J、Q、K 和 A。
public struct Deck: Equatable {
fileprivate var cards: [PlayingCard]
/// 返回一副扑克牌。
public static func standard52CardDeck() -> Deck {
var cards: [PlayingCard] = []
for rank in Rank.allCases {
for suit in Suit.allCases {
cards.append(PlayingCard(rank: rank, suit: suit))
}
}
return Deck(cards)
}
/// 创建一副扑克牌。
public init(_ cards: [PlayingCard]) {
self.cards = cards
}
/// 随机打乱一副扑克牌。
public mutating func shuffle() {
cards.shuffle()
}
/// 从牌组中发一张牌。
///
/// 该函数返回牌组中的最后一张牌。
public mutating func deal() -> PlayingCard? {
guard !cards.isEmpty else { return nil }
return cards.removeLast()
}
/// 牌组中剩余的牌数。
public var count: Int {
cards.count
}
}
// MARK: - ExpressibleByArrayLiteral
extension Deck: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: PlayingCard...) {
self.init(elements)
}
}
|
用 swift build 构建这个包,用 swift test 运行与该包关联的全部测试。默认模板还包含默认使用 swift-testing 的结构,其中包含一个空白但可用的测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
| [1/1] Planning build
Building for debugging...
[10/10] Linking DeckOfPlayingCardsPackageTests
Build complete! (2.68s)
Test Suite 'All tests' started at 2025-10-09 13:12:08.094.
Test Suite 'All tests' passed at 2025-10-09 13:12:08.095.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds
◇ Test run started.
↳ Testing Library Version: 1400
↳ Target Platform: arm64e-apple-macos14.0
◇ Test example() started.
✔ Test example() passed after 0.001 seconds.
✔ Test run with 1 test in 0 suites passed after 0.001 seconds.
|
为你的包添加测试
扩展测试,让它们能验证你库中的代码。更新 Tests/DeckOfPlayingCardsTests/DeckOfPlayingCardsTests.swift 的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| import DeckOfPlayingCards
import PlayingCard
import Testing
struct DeckTests {
@Test
func standard52CardDeck() {
var countByPlayingCard: [PlayingCard: Int] = [:]
var deck = Deck.standard52CardDeck()
while let playingCard = deck.deal() {
countByPlayingCard[playingCard, default: 0] += 1
}
#expect(countByPlayingCard.count == 52)
#expect(countByPlayingCard.values.allSatisfy({ $0 == 1 }))
for rank in Rank.allCases {
for suit in Suit.allCases {
let playingCard = PlayingCard(rank: rank, suit: suit)
#expect(countByPlayingCard[playingCard] == 1)
}
}
}
@Test
func deal() {
let playingCard = PlayingCard(rank: .ace, suit: .clubs)
var deck: Deck = [playingCard]
#expect(deck.deal() == playingCard)
#expect(deck.deal() == nil)
}
@Test
func countEmptyDeckHasZeroCards() {
let deck = Deck()
//XCTAssertEqual(deck.count, 0)
#expect(deck.count == 0)
}
@Test
func countStandard52CardDeckHas52Cards() {
let deck = Deck.standard52CardDeck()
#expect(deck.count == 52)
}
@Test
func countDealingDecreasesCountByOne() throws {
var deck = Deck([
PlayingCard(rank: .ace, suit: .spades), PlayingCard(rank: .queen, suit: .hearts),
])
#expect(deck.count == 2)
try #require(deck.deal() != nil)
#expect(deck.count == 1)
}
}
|
之后再运行测试,就能看到每个测试及其结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| Building for debugging...
[6/6] Linking DeckOfPlayingCardsPackageTests
Build complete! (0.44s)
Test Suite 'All tests' started at 2025-10-09 13:16:44.052.
Test Suite 'All tests' passed at 2025-10-09 13:16:44.052.
Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds
◇ Test run started.
↳ Testing Library Version: 1400
↳ Target Platform: arm64e-apple-macos14.0
◇ Suite DeckTests started.
◇ Test deal() started.
◇ Test countStandard52CardDeckHas52Cards() started.
◇ Test countEmptyDeckHasZeroCards() started.
◇ Test standard52CardDeck() started.
◇ Test countDealingDecreasesCountByOne() started.
✔ Test countDealingDecreasesCountByOne() passed after 0.001 seconds.
✔ Test deal() passed after 0.001 seconds.
✔ Test countStandard52CardDeckHas52Cards() passed after 0.001 seconds.
✔ Test countEmptyDeckHasZeroCards() passed after 0.001 seconds.
✔ Test standard52CardDeck() passed after 0.001 seconds.
✔ Suite DeckTests passed after 0.001 seconds.
✔ Test run with 5 tests in 1 suite passed after 0.001 seconds.
|
DeckOfPlayingCards 示例包的完整代码可以在 https://github.com/apple/example-package-deckofplayingcards 找到。
共享你的包
你可以在本地的其他 Swift 包中使用这个包,也可以通过 Git 托管服务共享它。当你想发布自己的包时,创建一个与语义化版本的主、次、补丁版本对应的 Git 标签,并把该标签推送到你的 Git 托管服务。例如,要用语义化版本 0.1(表示第一次次版本发布)给包打标签,就使用标签 0.1.0。
关于共享包的更多内容,参见发布与公开 Swift 包。
解析传递依赖
包管理器会解析你所用包及其所有传递依赖的依赖关系。另一个示例包 Dealer 通过使用本指南创建的包,展示了这一机制如何工作。你可以在线浏览这个示例包 https://github.com/swiftlang/example-package-dealer/,也可以把它下载到本地来探索:
1
2
| git clone https://github.com/swiftlang/example-package-dealer.git
cd example-package-dealer
|
dealer 包额外依赖 Swift Argument Parser,这是一个帮助解析命令行应用参数的工具包。
要查看依赖解析的过程与选择结果,请运行命令 swift package resolve。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| Fetching https://github.com/swiftlang/example-package-deckofplayingcards.git
Fetching https://github.com/apple/swift-argument-parser.git from cache
Fetched https://github.com/swiftlang/example-package-deckofplayingcards.git from cache (0.41s)
Fetched https://github.com/apple/swift-argument-parser.git from cache (0.58s)
Computing version for https://github.com/swiftlang/example-package-deckofplayingcards.git
Computed https://github.com/swiftlang/example-package-deckofplayingcards.git at 4.0.0 (0.94s)
Fetching https://github.com/apple/example-package-playingcard.git from cache
Fetched https://github.com/apple/example-package-playingcard.git from cache (0.30s)
Computing version for https://github.com/apple/example-package-playingcard.git
Computed https://github.com/apple/example-package-playingcard.git at 4.0.0 (0.66s)
Computing version for https://github.com/apple/swift-argument-parser.git
Computed https://github.com/apple/swift-argument-parser.git at 1.6.1 (0.39s)
Creating working copy for https://github.com/apple/swift-argument-parser.git
Working copy of https://github.com/apple/swift-argument-parser.git resolved at 1.6.1
Creating working copy for https://github.com/apple/example-package-playingcard.git
Working copy of https://github.com/apple/example-package-playingcard.git resolved at 4.0.0
Creating working copy for https://github.com/swiftlang/example-package-deckofplayingcards.git
Working copy of https://github.com/swiftlang/example-package-deckofplayingcards.git resolved at 4.0.0
|
当你运行 swift build 或 swift test 时,这一过程会自动发生,使依赖对你的项目可用。与前一个包一样,你可以用 swift build 构建这个包,并用命令 swift test 运行、查看该包的测试。
由于 dealer 包提供了一个命令行可执行文件,你还可以用 swift run 运行该包构建出来的可执行文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| [1/1] Planning build
Building for debugging...
[1/1] Write swift-version-2C315BDEC41BFF30.txt
Build of product 'dealer' complete! (0.13s)
Error: Missing expected argument '<count> ...'
OVERVIEW: Shuffles a deck of playing cards and deals a number of cards.
For each count argument, prints a line of tab-delimited cards to stdout,
or if there aren't enough cards remaining,
prints "Not enough cards" to stderr and exits with a nonzero status.
USAGE: dealer <count> ...
ARGUMENTS:
<count> The number of cards to deal at a time.
OPTIONS:
-h, --help Show help information.
|
指定可执行文件名以及所需的参数来试一试,例如 swift run dealer 5:
1
2
3
4
| Building for debugging...
[1/1] Write swift-version-2C315BDEC41BFF30.txt
Build of product 'dealer' complete! (0.07s)
♢ J ♢ 3 ♢ 7 ♣︎ 5 ♡ 7
|
包的构建产物默认也在 .build 目录中,你也可以在那里直接运行该工具。例如,dealer 包的调试构建(默认构建)位于 .build/debug/dealer,你可以在终端里调用它:.build/debug/dealer 5