from?https://xiaozhuanlan.com/topic/9635421780
目前,在 macOS/iOS 開(kāi)發(fā)中,我們通常使用?CocoaPods?或?Carthage?等非官方工具來(lái)管理項(xiàng)目工程中對(duì)第三方開(kāi)源庫(kù)的依賴。
Swift Package Manager(Swift 包管理器,一般簡(jiǎn)稱 SwiftPM 或者 SPM)是蘋(píng)果官方提供的一個(gè)用于管理源代碼分發(fā)的工具,旨在使分享代碼和復(fù)用其他人的代碼變得更加容易。該工具可以直接幫助我們編譯和鏈接 Swift packages(包),管理依賴關(guān)系、版本控制,以及支持靈活的代碼分發(fā)和團(tuán)隊(duì)協(xié)作。
關(guān)于 Swift Package Manager 的詳細(xì)介紹,可以參考去年的 WWDC 2018 Session 411:?Getting to Know Swift Package Manager,以及我總結(jié)的這篇文章:
WWDC 2018:細(xì)說(shuō) Swift 包管理工具
SwiftPM 一開(kāi)始僅支持 macOS 和 Linux 平臺(tái)上的 Swift 開(kāi)發(fā),且只能通過(guò)命令行的方式來(lái)使用。在最新的 Xcode 11 中集成了?libSwiftPM,并提供了圖形化操作界面,使 Swift Package 支持 iOS/watchOS/tvOS 等平臺(tái)。
下文我們將介紹如何在 Xcode 11 中使用 Swift Package 引入第三方開(kāi)源庫(kù)或私有代碼庫(kù)。
1. 如何添加 Package
我們先通過(guò) Xcode 11 新建一個(gè) Swift iOS 工程,假設(shè)名為?MyTestProject,然后可以通過(guò)如下兩種方式添加 Swift Package 依賴:
Xcode Menu -> File -> Swift Packages -> Add Package Dependency...

在 Xcode 工程中選中當(dāng)前 Project 名稱 -> 選擇 Swift Packages -> 點(diǎn)擊 + 圖標(biāo)添加

在彈出的窗口中,我們可以輸入要依賴的 Package 的 git 倉(cāng)庫(kù)地址,例如,我們這里要在工程中添加?Yams?這個(gè) Swift YAML 文件解析的開(kāi)源庫(kù),則可以在輸入框中填寫(xiě)其在 GitHub 上的 git url,如下圖所示:

此外,我們可以在 Xcode -> Preferences... -> Accounts 中添加并登錄自己的 GitHub/GitLab賬號(hào)或者公司內(nèi)部私有 Git 服務(wù)器賬號(hào),然后就可以在 Choose Package Repository 窗口中直接選擇你自己的或者已關(guān)注的 Swift Package,如下圖:

選擇好要依賴的 Package 后,點(diǎn)擊 Next 按鈕進(jìn)行版本號(hào)設(shè)置。我們可以指定 Package 的版本號(hào)范圍,規(guī)則如下,與 CocoaPods 類似:
Up to Next Major:?當(dāng)前指定的版本號(hào)到下一個(gè)大版本號(hào)之間的最新版本,例如 2.0.0 ~ 3.0.0(不包含 3.0.0)
Up to Next Minor:?當(dāng)前指定的版本號(hào)到下一個(gè)次版本號(hào)之間的最新版本,例如 2.0.0 ~ 2.1.0(不包含 2.1.0)
Range:?指定的兩個(gè)版本號(hào)之間的最新版本,例如 2.1.0 ~ 2.7.2(不包含 2.7.2)
Exact:?指定使用某一具體的版本號(hào)

同時(shí),我們也可以指定要依賴當(dāng)前 Package git 倉(cāng)庫(kù)的某一個(gè)分支或者某一次 commit。
最后,勾選當(dāng)前 Package 要添加到工程中的哪些 Targets,即可。

添加好 Package 之后,我們就可以在 Xcode 工程中查看到相關(guān)信息,如下圖:

接下來(lái)我們就可以在代碼中?import Yams?然后調(diào)用它的相關(guān) API 了。
2. Package 概覽
下面我們介紹一下 Swift Package 的內(nèi)部結(jié)構(gòu)。
一個(gè) Package(包)由 Swift 源碼文件和一個(gè)清單文件組成。這個(gè)清單文件被命名為?Package.swift,它使用?PackageDescription?模塊來(lái)定義包的名稱、內(nèi)容以及依賴關(guān)系。
以?Yams?為例,它包含的內(nèi)容如下:

Package.swift: 包的清單文件,用于描述包的名稱、內(nèi)容、依賴關(guān)系、支持的 Swift 版本號(hào);
Sources: 源碼文件夾,通常包括 C/C++ 代碼和 Swift 代碼等;
Tests: 單元測(cè)試代碼
而?Package.swift?文件的大致內(nèi)容如下:

另外,如果一個(gè) Package 依賴了另一個(gè) Package,也需要在?Package.swift?文件中進(jìn)行聲明。例如,有一個(gè) Package 叫 "DesignTheme",它依賴了 "DesignFont",則需要在 "DesignTheme" 的?Package.swift?文件中添加如下依賴代碼:
dependencies: [
.package(url:"http://github.com/WWDC19/DesignFont.git"),
]
此時(shí),當(dāng)在 Xcode 工程的 Swift Packages 中添加了 "DesignTheme",同時(shí)就會(huì)自動(dòng)下載依賴 "DesignFont",不需要我們?cè)偈謩?dòng)添加了。
因此,對(duì)于一個(gè) Xcode 工程,當(dāng)其依賴了一些 Swift Packages 后,在編譯鏈接時(shí),SwiftPM 會(huì)自動(dòng)編譯每個(gè) Package 并鏈接到主可執(zhí)行文件中。

PS:?對(duì)于 SwiftPM 的一些基本概念,例如:Modules、Packages、Products、Dependencies、Targets 等,在 Swift.org?官網(wǎng)已經(jīng)有非常詳細(xì)的描述和定義,另外,也可以參見(jiàn)我之前寫(xiě)的這篇文章,這里不再贅述。
3. Package 依賴詳解
本節(jié)我們?cè)敿?xì)介紹一下 Swift Package 之間互相依賴的一些細(xì)節(jié)。
如前面所述,假如我們?cè)?Demo 工程(名為 "Lunch")中,除了引入 "Yams" Package 外,又引入了一個(gè) "DesignTheme" Package,如下圖:

此時(shí),如果 "DesignTheme" Package 又同時(shí)依賴 "DesignFont" 和 "DesignColor" 兩個(gè)庫(kù),我們就會(huì)在 Xcode 工程的 Package 依賴列表中看到自動(dòng)引入了這兩個(gè)依賴的 Package:

那 Xcode 是如何找到他們的依賴關(guān)系和版本號(hào)信息呢?
正如前面所說(shuō)的,其實(shí)在 "DesignTheme" 的?Package.swift?文件中有詳細(xì)描述這些依賴關(guān)系,然后 Xcode 會(huì)根據(jù)該文件的信息來(lái)選擇下載對(duì)應(yīng) Package 的最佳版本號(hào):

另外一點(diǎn)需要注意的是,我們?cè)谏鲜?Demo 工程的 Swift Packages 中,僅添加了對(duì) “Yams” 和 "DesignTheme" 兩個(gè) Packages 的依賴,所以我們可以在工程代碼中直接?import Yams?和?import DesignTheme?然后調(diào)用它們相關(guān)的 API:

而另外兩個(gè) Packages "DesignFont" 和 "DesignColor" 是由 "DesignTheme" 的依賴隱式引入的,所以我們不能在代碼中直接 import 使用它們。
假如 Demo 工程中需要用到 "DesignFont" 相關(guān)的 API,則需要在 Xcode 工程的 Swift Packages 列表中顯式地添加依賴才行。
4. 如何更新 Package
當(dāng)我們的 Xcode 工程添加了一些 Swift Package 后,如果其中的一些 Package 有了新版本,例如,上述 "DesignFont" 我們一開(kāi)始依賴的是 1.2.0,后面其修復(fù)了 bug 并發(fā)布了新版本 1.2.1,我們?cè)撊绾胃履兀?/p>
其實(shí)非常方便,我們可以在 Xcode Menu 中點(diǎn)擊 File -> Swift Packages,然后選擇?"Update to Latest Package Versions"?即可,如下圖所示,此時(shí)當(dāng)前工程中所有的 Packages 都會(huì)自動(dòng)更新到指定版本范圍內(nèi)的最新版本。

那么,Xcode 更新一個(gè) Package 時(shí),除了下載更新最新的代碼外,還做了哪些事情呢?
其實(shí),Xcode 維護(hù)了一個(gè)?Package.resolved?文件用于記錄當(dāng)前工程已添加的各個(gè) Packages 的詳細(xì)信息,包含名稱、URL、分支和具體的版本號(hào)等信息,位于?.xcodeproj?里,路徑為:

Package.resolved?是一個(gè) JSON 文件,內(nèi)容大致如下:
{
"object": {
"pins": [
? ? ? {
"package":"Yams",
"repositoryURL":"git@github.com:jpsim/Yams.git",
"state": {
"branch":null,
"revision":"c947a306d2e80ecb2c0859047b35c73b8e1ca27f",
"version":"2.0.0"
? ? ? ? }
? ? ? },
? ? ? // other packages
? ? ]
? },
"version":1
}
當(dāng)某一個(gè) Package 更新時(shí),上述文件中該 Package 對(duì)應(yīng)的?revision?和?version?就會(huì)被修改為最新信息,如下圖所示,"DesignFont" 從 1.2.0 更新到 1.2.1:

所以,與 CocoaPods 的?Podfile.lock?和 Carthage 的?Cartfile.resolved?文件作用類似,當(dāng)一個(gè)工程涉及到多人協(xié)作開(kāi)發(fā)時(shí),我們就應(yīng)該在 git 倉(cāng)庫(kù)中提交?Package.resolved?文件,保證團(tuán)隊(duì)中每個(gè)人用的 Packages 都是同一個(gè)版本,避免帶來(lái)不必要的問(wèn)題。
5. 解決 Package 版本沖突
仍然以第三節(jié)舉的 Demo 工程為例,其中,我們依賴的 "DesignTheme" 的版本號(hào)設(shè)置為 1.0.0 - Next Major,而 "DesignTheme" 的?Package.swift?文件中聲明的兩個(gè)依賴 "DesignFont" 和 "DesignColor" 對(duì)應(yīng)的版本號(hào)也為 1.0.0 - Next Major,此時(shí),這 3 個(gè) Packages 在 Xcode 中最終被引入的版本號(hào)如下:

由于 "DesignTheme" 已經(jīng)隱式引入 1.2.0 版本的 "DesignFont" 了,這個(gè)時(shí)候,如果我們要在 Demo 工程的 Swift Packages 中直接顯式地引入 "DesignFont" 的 2.0.0 以上版本,就會(huì)報(bào)錯(cuò),存在版本沖突。

這是因?yàn)?,在一個(gè) Xcode 工程里(workspace),對(duì)于同一個(gè) Package,只能引入一個(gè)版本號(hào)。

那么如何解決這個(gè)問(wèn)題呢?

只能是更新 "DesignTheme" 到 2.0.0,并在其?Package.swift?文件中聲明依賴的 "DesignFont" 的版本號(hào)也從 2.0.0 起。然后,我們?cè)僭?Xcode 工程的 Swift Packages 列表中顯式地添加 2.0.0 起的 "DesignFont"。

6. 如何創(chuàng)建 Packages
本文主要講解如何在最新的 Xcode 11 中使用 Swift Package,如果你想學(xué)習(xí)如何創(chuàng)建一個(gè) Package 用于在 GitHub 上開(kāi)源或者團(tuán)隊(duì)內(nèi)部使用,可以閱讀以下兩個(gè)相關(guān) Session:
WWDC 2019 Session 410:?Creating Swift Packages
WWDC 2018 Session 411:?Getting to Know Swift Package Manager
7. 結(jié)語(yǔ)
在 GitHub 上,絕大部分 Swift 開(kāi)源庫(kù)都已經(jīng)支持通過(guò) SwiftPM 來(lái)接入,所以如果你的 Xcode 工程之前有使用一些第三方的 Swift 庫(kù),不妨改成用 Xcode 11 自帶的方式來(lái)導(dǎo)入試試吧。