引言
很多开发语言都有官方的包管理工具,例如Node.js的 NPM(Node Package Manager)。Cocoa中常用的的非官方第三方是 Cocoapods 和 Carthage 包管理工具。好消息是随着Swift的版本更新,一个官方的包管理工具诞生了!
Swift Package Manager(SPM)
Swift Package Manager Swift包管理器,简称SPM,是一个管理发布Swift代码的工具,它为 Swift 编译系统集成了自动下载、编译和连接依赖的过程。
SPM将在Swift3中发布,当前仅仅在Swift 3开发者预览版中可用
概念概述
本章节描述Swift Package Manager的一些基本概念和功能
模块 Module
Swift使用模块组织管理代码,每一个模块指定一个命名空间并强制指定模块外哪些部分的代码是可以被访问控制的。
一个程序可能在单个模块中拥有所有的代码,或者它可能导入其他模块作为依赖关。除了少数系统提供的模块,如Darwin在OS X和Linux上的Glibc,大部分依赖需要下载并构建代码来使用。
当你使用解决了一个特定问题的单独的模块代码,而且这些代码可以在其他情况下重用。例如,一个模块提供的网络请求功能, 可以使用在共享照片应用和一个天气应用中。使用模块可以让你的代码建立在其他开发者的代码之上,而不是你自己去重复实现相同的功能。
包 Packages
一个包由 Swift 源文件和一个清单文件组成。这个清单文件称为 Package.swift,定义包名或者它的内容使用 PackageDescription
模块。
一个包有一个或者多个 targets,每个targets指定一个product,并且可能声明一个或者多个依赖。
产物 Products
一个targets可能构建成一个库或者一个可执行文件。库包可以含被其他 Swift 代码导入的模块。可执行文件是一个可以被操作系统运行的程序。
依赖 Dependencies
targets的依赖是指包中代码必须添加的模块。依赖由包资源的绝对或者相对 URL 和一些可以被使用的包的版本要求所组成。包管理器的作用是通过自动为工程下载和编译所有依赖,减少协调的成本。这是一个递归的过程:依赖能有自己的依赖,其中每一个也可以具有依赖,形成了一个依赖相关图。包管理器下载和编译所需要满足整个依赖相关图的一切。
下一个章节保证你懂Swift。如果你是Swift新手,你可用访问你的入门的第一个资源 Swift Tour in The Swift Programming Language。如果你想跟上的代码示例,您将需要有一个快速的安装,你可以找到说明Getting Started。
使用示例 Example Usage
在在Getting Started,一个简单的““Hello, world!” 程序被建立在包管理器中。
为了了解 SPM 究竟能够做什么,我们来看一下下面这个由四个独立的包组成的例子:
- PlayingCard - Defines
PlayingCard
,Suit
, andRank
types. - FisherYates - Defines an extension that implements the
shuffle()
andshuffleInPlace()
methods. - DeckOfPlayingCards - Defines a
Deck
type that shuffles and deals an array ofPlayingCard
values. - Dealer - Defines an executable that creates a
DeckOfPlayingCards
, shuffles it, and deals the first 10 cards.
你可以从 Dealer project from GitHub编译并运行完整例子,然后运行如下命令:
1 2 3 |
$ cd example-package-dealer $ swift build $ .build/debug/Dealer |
创建一个库包 Creating a Library Package
我们将从创建一个代表一副标准的52张扑克牌的模块开始。 PlayingCard
模块定义了 由 Suit
枚举值(Clubs, Diamonds, Hearts, spades)和 Rank
枚举值(Ace, Two, Three, …, Jack, Queen, King)组成的 PlayingCard
类。各个类的核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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 } |
一般来说, 一个包包括任何位于 Sources/
的源文件。
1 2 3 4 5 6 |
example-package-playingcard ├── Sources │ ├── PlayingCard.swift │ ├── Rank.swift │ └── Suit.swift └── Package.swift |
由于 PlayingCard
模块并不会生成可执行文件,所以应该称为 库 。 库表示被编译成一个可以被其他包导入的模块的包。默认情况下,库模块公开所有源代码中声明的公共类型和方法 在 Sources/
目录中。
运行 swift build
开始启动 Swift 编译的过程。如果一切工作正常,静态库 PlayingCard.a 将会在 .build/debug
目录下生成。
1 2 3 4 5 6 7 |
example-package-playingcard └── .build └── debug ├── PlayingCard.a ├── PlayingCard.o ├── PlayingCard.swiftdoc └── PlayingCard.swiftmodule |
完整 PlayingCard
代码可以在 https://github.com/apple/example-package-playingcard 中被找到。
使用编译配置语句 Using Build Configuration Statements
下一个即将编译的模块是 FisherYates
. 与 PlayingCard
不同,这个模块没有定义任何类型.而是, 它扩展了存在的类型 CollectionType
和 MutableCollectionType
协议 –增加了 shuffle()
方法和对应的 shuffleInPlace()
.
shuffleInPlace()
方法的实现使用了 Fisher-Yates 经典洗牌算法来随机交换集合中的元素. 因为Swfit标准库没有提供随机数生成器,该方法需要调用一个从系统模块导入的函数. 。为了使这个方法能够同时适配 OSX 和 Linux, 代码将要用到编译配置语句。
在 OS X, 系统模块 Darwin
,提供 arc4random_uniform(_:)
方法. 在 Linux, 系统的模块是 Glibc
, 提供 random()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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..<count - 1 { #if os(Linux) let j = Int(random() % (count - i)))) + i #else let j = Int(arc4random_uniform(UInt32(count - i))) + i #endif if i == j { continue } swap(&self[i], &self[j]) } } } |
The complete code for the
FisherYates
package can be found at https://github.com/apple/example-package-fisheryates.
导入依赖 Importing Dependencies
DeckOfPlayingCards
包把前两个包聚合到一起:它定义了一个在 PlayingCard
数组中使用 FisherYates
的 shuffle()
方法的 Deck
类型。
为了使用 FisherYates
和 PlayingCards
模块, DeckOfPlayingCards
包必须在 Package.Swift
清单中将上述模块声明为依赖。
1 2 3 4 5 6 7 8 9 10 11 12 |
import PackageDescription let package = Package( name: "DeckOfPlayingCards", targets: [], dependencies: [ .Package(url: "https://github.com/apple/example-package-fisheryates.git", majorVersion: 1), .Package(url: "https://github.com/apple/example-package-playingcard.git", majorVersion: 1), ] ) |
每一个依赖指定一个源地址URL和版本要求,源地址 URL 是指允许当前用户解析到对应的 Git 仓库,版本号要求遵循 语义化版本号 2.0.0 的约定,用来决定检出或者使用哪个 Git 标签版本来建立依赖。对于 FisherYates
和 PlayingCard
这两个依赖来说, 最新的将要被使用的主版本号为 1
(例如: 1.0.0)。
当运行 swift build
命令时,包管理器将会下载所有的依赖, 并将他们编译成静态库,再把它们链接到包模块中。这样将会使 DeckOfPlayingCards
可以访问它import
语句依赖的模块的公共成员。
你可以看到这些资源被下载到你工程根目录的 Packages
目录下,并且会生成编译products在你工程根目录的 .build
目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
example-package-deckofplayingcards ├── .build │ └── debug │ ├── DeckOfPlayingCards.a │ ├── DeckOfPlayingCards.o │ ├── DeckOfPlayingCards.swiftdoc │ ├── DeckOfPlayingCards.swiftmodule │ ├── FisherYates.a │ ├── FisherYates.o │ ├── FisherYates.swiftdoc │ ├── FisherYates.swiftmodule │ ├── PlayingCard.a │ ├── PlayingCard.o │ ├── PlayingCard.swiftdoc │ └── PlayingCard.swiftmodule └── Packages └── example-package-fisheryates-1.0.1 │ ├── Package.swift │ ├── README.md │ └── Sources └── example-package-playingcard-1.0.0 ├── Package.swift ├── README.md └── Sources |
包目录包含了所有依赖的克隆源. 这样将使你能修改源代码并直接推送这些修改到第三方的源,而不需要再对每个包在单独进行复制。剩下的步骤参考前面内容。
完整的 DeckOfPlayingCards
包可以在如下地址查看: https://github.com/apple/example-package-deckofplayingcards. 。
解决子依赖 Resolving Subdependencies
到了这, 你可以 build Dealer
模块. Dealer
模块依赖于 DeckOfPlayingCards
包, 而该模块又依赖于 PlayingCard
和 FisherYates
包。然而,由于 Swift Package Manager 会自动解决子依赖,所以你需要声明 DeckOfPlayingCards
包作为依赖即可。
1 2 3 4 5 6 7 8 9 10 |
import PackageDescription let package = Package( name: "Dealer", targets: [], dependencies: [ .Package(url: "https://github.com/apple/example-package-deckofplayingcards.git", majorVersion: 1), ] ) |
对于任意类型在代码中引用Swift 需要在源文件中导入模块 , 在 Dealer
模块的 main.swift
文件,DeckOfPlayingCards
中的 Deck
类型和 PlayingCard
的 PlayingCard
类型是引用。虽然 Deck
类型中的 shuffle()
方法使用在 FisherYates
模块内,但是 FisherYates
模块并不需要在 main.swift
中被导入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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) } |
按照惯例,一个包在根目录中包含一个叫做 main.swift
的文件生成一个可执行文件。
运行 swift build
命令开始用 Swift 编译来生成可以被运行在 .build/debug
目录下的可执行文件 Dealer
。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ swift build $ ./.build/debug/Dealer ♠︎6 ♢K ♢2 ♡8 ♠︎7 ♣︎10 ♣︎5 ♢A ♡Q ♡7 |
完整的 Dealer
代码可以在如下地址找到 https://github.com/apple/example-package-dealer.
更多关于 Swift Package Manager 信息查看文档: Swift package Manager project on GitHub
社区的建议
最初的Swift Package manager 发行版仅仅是一个开始,如果可能,我们邀请你参与并帮助我们建立更好的工具
为了帮住你开始我们的项目,我们已经准备了以下社区建议提供了目前执行的决定的一些背景,并为未来的功能开发提供方向。如果你有兴趣参与,这是你应该阅读的第一个文件。