Swift Package Manager 使用
參考資料
- Swift 包管理器介紹
- Swift Package Manager 添加資源文件
- Swift Package Manager使用總結
- 使用 Xcode 創(chuàng)建獨立 Swift 軟件包
- 蘋果開發(fā)者簡體中文文檔
- Swift Package Manager 是一個蘋果官方出的管理源代碼分發(fā)的工具,目的是更簡單的使用別人共享的代碼佃声。它會直接處理包之間的依賴管理竖般、版本控制诗茎、編譯和鏈接。從總體功能上來說,和 iOS 平臺上的 Cocoapods谱秽、Carthage 一樣。
- Swift Package Manager (SwiftPM) 是 Apple 推出的一個包管理工具, 用于創(chuàng)建, 使用 Swift 的庫, 以及可執(zhí)行程序的工具.
- Xcode 11 開始自集成了 libSwiftPM摹迷,這樣一來疟赊,iOS、watchOS峡碉、tvOS 等平臺也都可以使用了
SwiftPM 包使用步驟如下:
1. SwiftPM 包的創(chuàng)建
2. 配置信息(添加依賴, target配置, 添加資源文件, 本地化多語言, 支持系統版本配置)
3. 開發(fā)編譯測試
4. SPM包上傳到云端
5. 在Swift項目中使用
1. SwiftPM的創(chuàng)建
SwiftPM 管理的每個 Package相當于 Xcode.Project近哟,并且有具體的代碼定義,包的目錄下必須含有 Package.swift 和 Sources代碼文件夾(鏈接系統的包除外)鲫寄。Package.swift 是整個 Package 的配置項吉执,類似 Cocoapods 中 .podspec 和 .podfile 的集合體。下面介紹下 SwiftPM 的簡單創(chuàng)建和使用地来。
創(chuàng)建一個可執(zhí)行的包
執(zhí)行以下命令
? mkdir MyPackage
? cd MyPackage
? swift package init --type executable
? swift build
? swift run
Hello, World!
--type 參數
- empty(空包):
- Source 文件夾下什么都沒有戳玫,也不能編譯
- library(靜態(tài)包):
- Source 文件夾下有個和包同名 swift 文件,里面有個空結構體
- executable(可執(zhí)行包):
- Source 文件夾下有個 main.swift 文件未斑,在 build 之后會在 .build/debug/ 目錄下生成一個可執(zhí)行文件咕宿,可通過 swift run 或者直接點擊運行,從而啟動一個進程
- system-module(系統包):
- 這種包是專門為了鏈接系統庫(例如 libgit蜡秽、jpeglib府阀、mysql 這種系統庫)準備的,本身不需要任何代碼芽突,所以也沒有 Source 文件夾肌似,但是需要編輯 module.modulemap 文件去查找系統庫路徑 (Swift 4.2 已經被其他方式取代)
2. 配置信息
添加依賴
如果需要依賴其他的包, 需要在 Package.swift 定義依賴項和版本诉瓦,像下面這樣:
// swift-tools-version:4.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "WYMobileWebsite",
dependencies: [
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.0"),
.package(url:"https://github.com/PerfectlySoft/Perfect-MySQL.git", from: "3.0.0"),
.package(url:"https://github.com/PerfectlySoft/Perfect-Session.git", from: "3.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "WYMobileWebsite",
dependencies: ["PerfectHTTPServer","PerfectMySQL","PerfectSession"]),
.testTarget(
name: "WYMobileWebsiteTests",
dependencies: ["WYMobileWebsite"]),
]
)
Package.dependencies 用于添加包的依賴川队,一般是包括指向包源的 git 路徑和版本環(huán)境力细,或指向依賴包的本地路徑.
在執(zhí)行 Swift build 時會自動執(zhí)行一個 swift package resolve 命令,該命令會解析 Package.swift 的依賴固额,并生成對應的 package.resolved 文件眠蚂,下面有介紹。
添加依賴支持如下五種方式
直接從枚舉的定義中就可以看出 Package.dependencies 支持如下五種方式:
- git 源 + 確定的版本號
- git 源 + 版本區(qū)間
- git 源 + Commit 號
- git 源 + 分支名
- 本地路徑
.package(url: "https://github.com/Alamofire/Alamofire.git", .exact("1.2.3")),
.package(url:"https://github.com/Alamofire/Alamofire.git", .branch("master")),
.package(url:"https://github.com/Alamofire/Alamofire.git", from: "1.2.3"),
.package(url: "https://github.com/Alamofire/Alamofire.git", .revision("e74b07278b926c9ec6f9643455ea00d1ce04a021"),
.package(url: "https://github.com/Alamofire/Alamofire.git", "1.2.3"..."4.1.3"),
.package(path: "../Foo"),
添加系統依賴包(不做介紹)
Package.SupportedPlatform(系統支持版本)
這個 Struct 用于設置包的最小依賴平臺版本斗躏,具體 API 定義可以進入代碼文檔中查看逝慧,下面給出示例:
platforms: [.macOS(.v10_10)],
需要注意的是雖然這個屬性是個數組,但是目的是為了讓設置不同平臺的最小依賴啄糙,如果設置了多個同平臺的值進去笛臣,就會報錯,例如這樣:[.macOS(.v10_10), .macOS(.v10_11)]
error: manifest parse error(s):found multiple declaration for the platform: macOS
Package.Product
Product 是 Package 編譯后對外的產品輸出隧饼,一般可分為兩種類型:
- 可執(zhí)行文件
- 靜態(tài)庫或者動態(tài)庫
[圖片上傳失敗...(image-a7515d-1596512368229)]
當執(zhí)行完 Swift build 之后沈堡,就會在 .build/debug 下生成對應的可執(zhí)行文件 tool 和靜態(tài)庫 libPaperStatic.a、動態(tài)庫 libPaperDynamic.dylib燕雁。
Package.Target
target 是 Package 的基本構件诞丽,和 xcodeproject 一樣,Package 可以有多個 target拐格。
target 分為三種類型:
- 常規(guī)型僧免、
- 測試類型、
- 系統庫類型捏浊。
分別對應下面幾個快捷創(chuàng)建方式:
/// The type of this target.
public enum TargetType : String, Encodable {
case regular
case test
case system
}
public static func target(name: String, dependencies: [PackageDescription.Target.Dependency] = [], path: String? = nil, exclude: [String] = [], sources: [String]? = nil, publicHeadersPath: String? = nil, cSettings: [PackageDescription.CSetting]? = nil, cxxSettings: [PackageDescription.CXXSetting]? = nil, swiftSettings: [PackageDescription.SwiftSetting]? = nil, linkerSettings: [PackageDescription.LinkerSetting]? = nil) -> PackageDescription.Target
public static func testTarget(name: String, dependencies: [PackageDescription.Target.Dependency] = [], path: String? = nil, exclude: [String] = [], sources: [String]? = nil, cSettings: [PackageDescription.CSetting]? = nil, cxxSettings: [PackageDescription.CXXSetting]? = nil, swiftSettings: [PackageDescription.SwiftSetting]? = nil, linkerSettings: [PackageDescription.LinkerSetting]? = nil) -> PackageDescription.Target
public static func systemLibrary(name: String, path: String? = nil, pkgConfig: String? = nil, providers: [PackageDescription.SystemPackageProvider]? = nil) -> PackageDescription.Target=
先介紹下 Target 的幾個主要的屬性
- name: 名字
- dependencies:
- 依賴項懂衩,注意不要和上面的 Package.Dependency 搞混了,不是一個東西金踪,這里可以依賴上面 Package.Dependency 的東西或者依賴另一個 target勃痴。所以這里只需要寫 Package 或者 Target 的名字字符串(Target.Dependency 這個枚舉也實現了 ExpressibleByStringLiteral)。
- path:
- target 的路徑热康,默認的話是 [PackageRoot]/Sources/[TargetName]沛申。
- source:
- 源文件路徑,默認 TargetName 文件夾下都是源代碼文件姐军,會遞歸搜索
- exclude:
- 需要被排除在外的文件/文件夾铁材,這些文件不會參與編譯。
- publicHeadersPath:
- C 家族庫的公共頭文件地址奕锌。
- swiftSettings:
- 定義一個用于特定環(huán)境(例如 Debug)的宏著觉,需要設置的話可以去 API 上研究下
- linkerSettings:
- 用于鏈接一些系統庫
添加資源文件
從更新到現在,SwiftPM 令人詬病的一個問題就是無法在包里添加資源文件惊暴。
配置要求
- Swift 5.3
- Xcode 12
對于一些使用目的明確的文件類型饼丘,比如下面圖中的這些。開發(fā)者不需要在 package.swift 文件中配置任何東西辽话,因為 Xcode 知道這些類型的文件是代表什么肄鸽,比如 .xcassets 文件代表圖片卫病、顏色資源, xib 代表用戶界面文件等
而對于一些使用目的不太明確的文件類型(如下圖中的一些文件類型)典徘,則需要在 package.swift 文件中配置蟀苛。例如純文本文件,這種文件中的數據可能是需要在運行時被加載而計算或者展示逮诲,也可能只是一個開發(fā)者文檔帜平。
對于上面這種意義不明的文件,就需要在 package.swift 清單中根據規(guī)則配置梅鹦,下面以這個 GameLogin 作為例子:
- 對于 Media.xcasset 和 main.storyboard 文件裆甩,Xcode 能明確知道它代表什么,所以不需要在這個配置文件中標記
- internal Note.txt 文件和 Artwork Creation 文件夾是模塊內部文件齐唆,所以寫在 target 的 exclude 屬性中嗤栓,這樣 Xcode 就不會把它編譯到包里
- 其他不能自動識別的類型并且需要被加載到 package 里的文件則配置在 resource 屬性中。
上面就是配置資源文件的一些規(guī)則蝶念,其中我們可以看到對于 resource 屬性抛腕,有兩個靜態(tài)方法: process() 和 copy() 芋绸。根據 session 中的介紹媒殉, process() 是推薦的方式,它所配置的文件會根據具體使用的平臺和內置規(guī)則進行適當的優(yōu)化摔敛。比如在運行時將 storyboard 或者 asset catalog 轉換成適當的形式廷蓉,也包括壓縮圖片等。如果文件類型無法識別马昙,或者不能根據平臺做任何優(yōu)化桃犬,就只會被簡單的拷貝,也就是 copy() 行楞。
構建過程
當一個 App 使用 package 時攒暇,這個 package 包括源文件和資源文件。在編譯時首先會將 Package 中每個 target 的源文件編譯成 module 鏈接到 App 中子房,然后這些 target 中的資源文件則會被加工成 bundle 放到這些 module 中形用。
在 Apple 平臺中,App 和 App extension 都是 bundle 集合证杭,這些 package 的 bundle 就是 App 的一部分田度,所以不需要做其他處理,就能在運行時獲取這些 bundle解愤。
當被編譯到一個 unbundle 產物時镇饺,比如腳本工具,則需要在腳本啟動的同時加載資源 bundle(這一步的具體步驟還不太理解)
訪問資源文件
在編譯有資源文件的 Package 中送讲,會自動創(chuàng)建并添加到 module 中一個文件: resource_bundle_accessor.swift 奸笤,里面的內容大概等價于下面這樣:
import Foundation
extension Bundle {
static let module = Bundle(path: "\(Bundle.main.bundlePath)/path/to/this/targets/resource/bundle")
}
對于 Swift 和 OC 分別可以使用下面這種方式惋啃,當然也可以使用 UIImage 自己的帶有 Bundle 參數的 Api
由于 module 是內部屬性,所以這種方式只能訪問自己模塊內部的資源文件揭保,無法跨模塊訪問肥橙。如果想在一個公共模塊提供外部模塊使用的資源,則需要自己創(chuàng)建一個資源訪問器秸侣。關于這一點存筏,使用過 Cocoapods 的 resource_bundle 功能的開發(fā)者可能比較了解,可以采用 bundle 路徑方式訪問味榛。如果不單獨建立一個公共資源模塊椭坚,則不需要考慮這么多。
小結
- 對于使用目的明確的文件搏色,比如以 .xcassets善茎、 .xib 、 .storyboard 等為后綴的文件频轿,不需要在 package.swift 中添加任何配置垂涯。
- 對于用途不明確的文件,比如純文本文件航邢、腳本文件耕赘,則視情況在 package.swift 中使用不同屬性配置(以下均是文件、文件夾均可配置):
- 對于不需要被外部引用的膳殷,例如內部的開發(fā)者文檔 README 操骡,需要配置在 target.excludes 屬性中。
- 對于運行時有用到赚窃,可以被系統根據平臺優(yōu)化的文件册招,比如各種圖片,需要配置在 target.resource.process 屬性里
- 對于運行時有用到勒极,不存在優(yōu)化的文件是掰,比如各種圖片,需要配置在 target.resource.copy 屬性里
本地化
首先需要在配置文件中配置默認的語言:
let package = Package(
name: "MyLibrary",
defaultLocalization: "en",
products: [
],
dependencies: [
],
targets: [
]
)
然后根據你需要的語言創(chuàng)建對應的文件夾辱匿,文件名為對應的語言键痛,后綴命名成 .lproj ,并在文件夾中創(chuàng)建 .strings 或者 .stringsdict 文件掀鹅,如下圖所示:
使用時:
Button(action: roll, label: {
Text("Roll", bundle: Bundle.module)
.font(.title)
})
小結
本地化過程散休,首先需要在配置文件中聲明默認語言,然后根據語言創(chuàng)建 .lproj 文件夾乐尊,再在文件夾里創(chuàng)建 .strings 或者 .stringsdict 文件戚丸,寫上本地化的字符串。
需要注意的小點:
通過預處理命令區(qū)分編譯環(huán)境
上文也說過,Package 可以通過 SwiftPM 執(zhí)行 swift build 進行編譯限府,也可以通過生成 xcodeproj 從而通過 Xcode 進行編譯夺颤,兩者的編譯環(huán)境并不相同,生成的可執(zhí)行文件也不是同一個地址胁勺,所以可以通過 SWIFT_PACKAGE 區(qū)分編譯環(huán)境
#if SWIFT_PACKAGE
import Foundation
#endif
選擇特定 Swift 版本的 Package
有條件的添加依賴
有些 Dependency 只希望在 Linux 環(huán)境下被依賴世澜,其他環(huán)境下不被依賴。這個特性已經被提到了 這里署穗,希望用如下的這種方式:
.package(url: "https://...", from: "1.0.0", when: .testing),
.package(url: "https://...", from: "2.0.0", when: .os(.linux),
Package.resolved
SwiftPM 也會生成一個 Package.resolved 文件來記錄依賴項的解析結果寥裂,當執(zhí)行依賴解析的時候,會優(yōu)先解析這個文件案疲,不存在時才會解析 Package.swift封恰。
這一點和 Cocoapods 的 podfile.lock 文件類似,有的項目進行工程管理時為了能每個成員自由的執(zhí)行 Update 操作褐啡,都會在上傳時把它忽略掉诺舔。
添加資源文件
Swift 語言里的 PackageDescription 里(即 Swift 5.1)
- 配置要求
- Swift 5.3
- Xcode 12
3. 開發(fā)編譯測試
采用 Xcode 運行
打開Xcode, 默認 swift build 是不會生成 packageName.xcodeproj 這種 Xcode 可以直接打開的工程文件.
但是可以通過
<font color="red">swift package generate-xcodeproj</font>
命令行生成一個 .xcodeproj 文件, 然后就可以通過 Xcode 運行該項目了,如果需要配置什么環(huán)境變量备畦,則需要通過 Build Setting 中的選項配低飒。
- 需要注意的一點事,通過 swift run 和通過 Xcode 啟動的是不同的進程懂盐,兩種方式生成的可執(zhí)行文件并不是同一個褥赊,所以如果需要把可執(zhí)行文件更新到其他地方的時候注意別弄錯了。
集成到工程中, 邊開發(fā)邊測試
- 執(zhí)行swift package generate-xcodeproj命令,這里會生成Dependencies.xcodeproj
- 打開iOS工程允粤,將第1步生成的project拖入工程作為sub-project崭倘,
- 添加依賴的framework
- 驗證引入包是否成功
4. SPM包上傳到云端
跟普通項目上傳到第三方托管平臺一樣, SPM包上傳到云端并不要想cocoapods一樣執(zhí)行相應的終端指令.
步驟:
- 代碼編譯通過沒問題
- 上傳到遠程托管平臺
- 打上tag(重要)
5. 在 Swift 項目中使用
- 在 Xcode 導航 File -> Swift Packages -> Add Package Dependency...
- 如下圖:
- 通過倉庫連接查找.
- 通過登錄 git 賬號查找
- 指定倉庫分支和版本號
- 在 Project 中查看已經添加的spm包
- 在代碼 import spm包使用
SwiftPM 在 iOS 平臺的使用
其實說 SPM 支持 iOS 等平臺翼岁,個人覺得是有點問題的类垫,因為這里只是 Xcode11 集成了 libSwiftPM,適配了 SPM 系統琅坡,從 SPM 本身的設計來看悉患,并不能嚴格的說支持 iOS 等平臺。
SwiftPM 對比 Cocoapods 和 Carthage
Cocoapods
使用最廣泛的工具榆俺,依賴放在各個源(master 或者 自己的源)上的 podspec 文件進行下載代碼庫售躁,在本地生成一個 workspace 進行統一管理、添加依賴茴晋。
- 自動化/侵入性高:一鍵配置和集成依賴/自動更改 Xcode.project 的配置
- 中心化:提供各個源管理倉庫配置文件陪捷,所有更新倉庫文件索引可能會很慢。
- 緩存:除了項目根目錄的緩存之外诺擅,還有較完整的本地緩存體系市袖,所以不同工程下載同一個庫時會直接從本地拿。
Carthage
- 去中心化:沒有統一管理的中心烁涌,所以沒有更新中心服務器的文件索引這種耗時步驟
- 成本略高/侵入性低:Carthage 只會幫你把各個庫下載到本地(默認會編譯成靜態(tài)庫)苍碟,具體的 Project 配置需要自己弄
- 生態(tài)環(huán)境:很差酒觅,很多庫都沒有提供這種依賴方式
- 緩存:只有項目根目錄的緩存,所以不同項目對于同一個庫需要重新下載
SwiftPM
- 去中心化:沒有統一管理的中心微峰,所以沒有更新中心服務器的文件索引這種耗時步驟
- 自動化/侵入性高:默認情況下需要有一定的目錄格式
- 生態(tài)環(huán)境:怎么說呢舷丹,不能說差,只能說不夠成熟蜓肆,還有很多待優(yōu)化項颜凯,畢竟是官方開發(fā),Xcode 自集成
- 緩存:只有項目根目錄的緩存仗扬,所以不同項目對于同一個庫需要重新下載