概念概述
原文
Package Manager
Modules模块
在 Swift 中我们使用模块来管理代码,每个模块指定一个命名空间并强制指定模块外哪些部分的代码是可以被访问控制的。
一个程序可以将它所有代码聚合在一个模块中,也可以将它作为依赖关系导入到其他模块。除了少量系统提供的模块,像 OS X 中的 Darwin 或者 Linux 中的 Glibc 等的大多数依赖需要代码被下载或者内置才能被使用。
当你将编写的解决特定问题的代码独立成一个模块时,这段代码可以在其他情况下被重新利用。例如,一个模块提供了发起网络请求的功能,在一个照片分享的 app 或者 一个天气的 app 里它都是可以使用的。使用模块可以让你的代码建立在其他开发者的代码之上,而不是你自己去重复实现相同的功能。
Packages包
一个包由 Swift 源文件和一个清单文件组成。这个清单文件称为 Package.swift
,定义包名或者它的内容使用PackageDescription
模块。
一个包有一个或者多个目标,每个目标指定一个产品并且可能声明一个或者多个依赖。
import PackageDescription
let package = Package(
name: "DeckOfPlayingCards",
products: [
.library(name: "DeckOfPlayingCards", targets: ["DeckOfPlayingCards"]),
.executable(name: "Dealer", targets: ["Dealer"]),
],
dependencies: [
.package(url: "https://github.com/apple/example-package-fisheryates.git", from: "2.0.0"),
.package(url: "https://github.com/apple/example-package-playingcard.git", from: "3.0.0"),
],
targets: [
.target(
name: "DeckOfPlayingCards",
dependencies: ["FisherYates", "PlayingCard"]),
.testTarget(
name: "DeckOfPlayingCardsTests",
dependencies: ["DeckOfPlayingCards"]),
]
)
Products产品
一个target
可能构建一个.library()库
或者一个.executable()可执行文件
作为其产品。
.library(name: "", targets: [])
库:是包含用于其他Swift 代码导入该模块
.executable(name: "", targets: [])
可执行文件:是一段可以被操作系统运行的程序。
Dependencies依赖
Dependencies
依赖是指Package
中代码必须添加的Modules
块。Dependencies
由.package
资源的绝对路径
或相对 URL
和包的版本
组成。
包管理器的作用是通过自动为工程下载和编译所有依赖的过程中,减少协调的成本。这是一个递归的过程:依赖能有自己的依赖,其中每一个也可以具有依赖,形成了一个依赖相关图。
用例
创建依赖库lib Package
- 建一个
target
表示标准的52张扑克牌的PlayingCard
:
mkdir PlayingCard
cd PlayingCard
swift package init
- 创建公共实现类
PlayingCard
定义PlayingCard
类型,它由Suit
枚举值(梅花、方块、红心、黑桃)和Rank
枚举值(Ace、2、3、…、Jack、Queen、King)组成。
public enum Rank : Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
}
public enum Suit: String {
case Spades, Hearts, Diamonds, Clubs
}
public struct PlayingCard {
let rank: Rank
let suit: Suit
}
按照约定,一个target
所有的源文件都存在相应的Sources/
目录下,默认情况下,库模块
使用public
来声明类型和方法,这些类型和方法位于Sources/
中:
example-package-playingcard
├── Sources
│ └── PlayingCard
│ ├── PlayingCard.swift
│ ├── Rank.swift
│ └── Suit.swift
└── Package.swift
PlayingCard
因为不生成executable
可执行文件,这种类型的target
成为库
,当构建一个模块可以被依赖导入使用。
- 运行
swift build
命令,将编译生成PlayingCard
模块。
使用 #if #else #endif
构建配置语句
- 创建
fisher
模块
mkdir fisher
cd fisher
swift package init
fisher
与PlayingCard
不同,此模块不定义任何新类型。相反,它扩展了现有的类型(特别是CollectionType
和MutableCollectionType
协议),以添加shuffle()
方法和它的变体副本shuffleInPlace()
。
- 使用构建配置语句
#if #else #endif
shuffleInPlace()
的实现使用Fisher-Yates
算法随机排列集合中的元素。因为Swift标准库不提供随机数生成器,所以该方法必须调用从系统模块导入的函数。为了使该函数与macOS
和Linux
兼容,代码使用构建配置语句。
在macOS
中,系统模块是Darwin
,它提供了arc4random_uniform(_:)
函数。
在Linux
中,系统模块为Glibc
,它提供了random()
函数:
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
public extension MutableCollectionType where Index == Int {
mutating func shuffleInPlace() {
if count <= 1 { return }
for i in 0..
依赖库导入及使用
DeckOfPlayingCards
包将前两个包组合在一起:它定义了一种桥牌类型,该类型在一个PlayingCard
值数组上使用fisher()
方法。
要使用fisher
和PlayingCards
模块,必须在DeckOfPlayingCards
模块的清单文件Package.swift
中声明为它们package
依赖项。
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "DeckOfPlayingCards",
products: [
.library(name: "DeckOfPlayingCards", targets: ["DeckOfPlayingCards"]),
],
dependencies: [
.package(url: "https://github.com/apple/example-package-fisheryates.git", from: "2.0.0"),
.package(url: "https://github.com/apple/example-package-playingcard.git", from: "3.0.0"),
],
targets: [
.target(
name: "DeckOfPlayingCards",
dependencies: ["FisherYates", "PlayingCard"]),
.testTarget(
name: "DeckOfPlayingCardsTests",
dependencies: ["DeckOfPlayingCards"]),
]
)
每个依赖项指定源URL
和from:
版本号。
源URL
是解析到Git存储库的当前用户可以访问的URL。版本需求遵循语义版本控制(SemVer)约定,用于确定签出哪个Git标签并使用它来构建依赖关系。对于依赖项FisherYates
,将使用最新版本主版本为2(例如,2.0.4)。类似地,PlayingCard
依赖项将使用最新版本主版本为3。
当运行swift build
命令时,包管理器下载所有依赖项,编译、并链接到包模块。这样DeckOfPlayingCards
使用import
语句来访问依赖模块中public
类型的属性和方法。
您可以在项目根目录下的.build/checkouts
目录中看到下载的源代码,在项目根目录下的.build
目录中看到中间的构建产品。
解决依赖传递关系
构建Dealer
模块,它依赖于DeckOfPlayingCards
包,而DeckOfPlayingCards
包又依赖于PlayingCard
和fisher
包。然而,由于Swift包管理器自动解析传递依赖项,您只需将DeckOfPlayingCards
包声明为依赖项即可。
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "dealer",
products: [
.executable(name: "Dealer", targets: ["Dealer"]),
],
dependencies: [
.package(url: "https://github.com/apple/example-package-deckofplayingcards.git", from: "3.0.0"),
],
targets: [
.target(
name: "Dealer",
dependencies: ["DeckOfPlayingCards"]),
]
)
依赖传递关系也体现在Swift源文件代码import
导入一个模块,就可以使用该某块下所有依赖库的类型。例如Dealer
模块的main.swift
文件。在DeckOfPlayingCards
中的Deck
类型和PlayingCard
中的PlayingCard
类型。尽管Deck
类型的shuffle()
方法在内部使用了fisher
模块,但该模块不需要在main.swift
中导入。
import PlayingCard
import DeckOfPlayingCards
let numberOfCards = 10
var deck = Deck.standard52CardDeck()
deck.shuffle()
for _ in 1...numberOfCards {
guard let card = deck.deal() else {
print("No More Cards!")
break
}
print(card)
}
构建并运行可执行文件
根据约定,一个target
的根目录中包含一个名为main.swift
文件,可以构建成一个可执行文件。
运行swift build
命令,然后运行.build/debug
目录下的Dealer
可执行文件。
$ swift build
$ ./.build/debug/Dealer
︎6
K
2
8
︎7
︎10
︎5
A
Q
7
两个坑
swift package generate-xcodeproj
- 每次更新或添加库或框架,xcodeproj就需要重新创建一次,不然无法引用到新库或新框架;
- 在source文件夹如果发生任何改动,库或框架的更新就会失败。