現今 Swift 包中的二進位制目標

語言: CN / TW / HK

文章目錄

  1. 理解二進位制在 Swift 中的演變
  2. 命令列工具相關
  3. 結論

iOSmacOS 開發中, Swift 包現在變得越來越重要。Apple 已經努力推動橋接那些縫隙,並且修復那些阻礙開發者的問題,例如阻礙開發者將他們的庫和依賴由其他諸如 CarthageCocoaPods 依賴管理工具遷移到 Swift 包依賴管理工具的問題,例如沒有能力新增構建步驟的問題。這對任何依賴一些程式碼生成的庫來說都是破壞者,比如,協議和 Swift 生成。

理解二進位制在 Swift 中的演變

為了充分理解 Apple 的 Swift 團隊在二進位制目標和他們引入的一些新 API 方面採取的一些步驟,我們需要理解它們從何而來。在後續的部分中,我們將調研 Apple 架構的演變,以及為什麼二進位制目標的 API 在過去幾年中逐漸形成的,特別是自 Apple 釋出了自己的矽晶片之後。

胖二進位制和 Frameworks 框架

如果你曾必須處理二進位制依賴,或者你曾建立一個屬於你自己的可執行檔案,你將會對 胖二進位制 這個術語感到熟悉。這些被擴充套件(或增大)的可執行檔案,是包含了為多個不同架構原生構建的切片。這允許庫的所有者分發一個執行在所有預期的目標架構上的單獨的二進位制。

當原始碼不能被暴露或當處理非常龐大的程式碼倉庫時,預編譯庫成為可執行檔案非常有意義,因為預編譯原始碼以及以二進位制檔案分發他們,將節省構建程式在他們的應用上的構建時間。

Pods 是一個非常好的例子,當開發者發現他們自己沒必要構建那些非常少改動的依賴。這是一個很共通的問題,它激發了諸如 cocoapods-binary 之類的專案,該專案預編譯了 pod 依賴項以減少客戶端的構建時間。

Frameworks 框架

嵌入靜態二進位制檔案可能對應用程式來說已經足夠了,但如果需要某些資源(如 assets 或標頭檔案),則需要將這些資源與包含所有切片的 胖二進位制檔案 捆綁在一起,形成所謂的 frameworks 檔案。

這就是諸如 Google Cast 之類的預編譯庫在過渡到使用 xcframework 進行分發之前所做的事情 —— 下一節將詳細介紹這種過渡的原因。

到目前為止,一切都很好。 如果我們要為分發預編譯一個庫,那麼胖二進位制檔案聽起來很理想,對吧?並且,如果我們需要捆綁一些其他資源,我們可以只使用一個 frameworks。 一個二進位制來統治他們所有!

XCFrameworks 框架

好吧,不完全是。胖二進位制檔案有一個大問題,那就是你不能有兩個架構相同但命令/指令不同的切片。 這曾經很好,因為裝置和模擬器的架構總是不同的,但是隨著 Apple Silicon 計算機 (M1) 的推出,模擬器和裝置共享相同的架構 (arm64),但具有不同的載入器命令。 這與面向未來的二進位制目標相結合,正是 Apple 引入 XCFrameworks 的原因。

你可以在 Bogo Giertler 撰寫的這篇精彩文章 中詳細瞭解為 iOS 裝置構建的 arm64 切片和為 M1 mac 的 iOS 模擬器構建的 arm64 切片之間的區別。

XCFrameworks 現在允許將多個二進位制檔案捆綁在一起,解決了 M1 Mac 引入的裝置和模擬器衝突架構問題,因為我們現在可以為每個用例提供包含相關切片的二進位制檔案。 事實上,如果我們需要,我們可以走得更遠,例如,在同一個 xcframework 中捆綁一個包含 iOS 目標的 UIKit 介面的二進位制檔案和一個包含 macOS 的 AppKit 介面的二進位制檔案,然後讓 Xcode 基於期望的目標架構決定使用哪一個。

在 Swift 包中,那先能夠以 binaryTarget 被包含進專案的,能夠在包中被引入任意其他目標。這相同的操作同樣適用於 frameworks

命令列工具相關

由於 Swift 5.6 版本中引入了用於 Swift 包管理器的 可擴充套件構建工具 ,因此可以在構建過程中的不同時間執行命令。

這是 iOS 社群長期以來一直強烈要求的事情,例如格式化原始碼、程式碼生成甚至收集公制程式碼庫的指標。 Swift 5.6 中所有這些所謂的 外掛 最終都需要呼叫可執行檔案來執行特定任務。 這是二進位制檔案再次在 Swift 包中參與的地方。

在大多數情況下,對於我們 iOS 開發人員來說,這些工具將來自同時支援 macOS 的不同架構切片 —— Apple Silicon 的 arm64 架構和 Intel Mac 的 x86_64 架構。開發者工具如, SwiftLintSwiftGen 正是這種案例。 在這種情況下,可以使用包含可執行檔案(本地或遠端)的 .zip 檔案的路徑建立新的二進位制目標。

注意可執行檔案必須在.zip檔案的根目錄下,否則找不到。

Artifact Bundles

到目前為止,命令列工具所採用的方法僅適用於 macOS 架構。但我們不能忘記,Linux 機器也支援 Swift 包。 這意味著如果要同時支援 M1 macs (arm64) 和 Linux arm64 機器,上面的胖二進位制方法將不起作用 —— 請記住,二進位制不能包含具有相同架構的多個切片。 在這個階段可能有人會想,我們可以不只使用 xcframeworks 嗎? 不,因為它們在 Linux 作業系統上不受支援!

Apple 已經考慮到這一點,除了引入 可擴充套件構建工具 之外,Artifact Bundles 和對二進位制目標的其他改進也作為 Swift 5.6 的一部分發布。

工件包(Artifact Bundles) 是包含 工件 的目錄。 這些工件需要包含支援架構的所有不同二進位制檔案。 二進位制檔案和支援的架構的路徑是使用清單檔案 (info.json) 指定的,該檔案位於 Artifact Bundle 目錄的根目錄中。 你可以將此清單檔案視為一個地圖或指南,以幫助 Swift 確定哪些可執行檔案可用於哪種架構以及可以在哪裡找到它們。

以 SwiftLint 為例

SwiftLint 在整個社群中被廣泛用作 Swift 程式碼的靜態程式碼分析工具。 由於很多人都非常渴望讓這個外掛在他們的 SwiftPM 專案中執行,我認為這將是一個很好的例子來展示我們如何將分發的可執行檔案從他們的釋出頁面變成一個與 macOS 架構和 Linux arm64 相容的工件包。

讓我們從下載兩個可執行檔案(macOSLinux)開始。

至此,bundle的結構就可以建立好了。 為此,建立一個名為 swiftlint.artifactbundle 的目錄並在其根目錄新增一個空的 info.json

shell mkdir swiftlint.artifactbundle touch swiftlint.artifactbundle/info.json

現在可以使用 schemaVersion 填充清單檔案,這可能會在未來版本的工件包和具有兩個變體的工件中發生變化,這將很快定義:

json { "schemaVersion": "1.0", "artifacts": { "swiftlint": { "version": "0.47.0", # The version of SwiftLint being used "type": "executable", "variants": [ ] }, } }

需要做的最後一件事是將二進位制檔案新增到包中,然後將它們作為變體新增到 info.json 檔案中。 讓我們首先建立目錄並將二進位制檔案放入其中(macOS 的一個在 swiftlint-macos/swiftlint,Linux 的一個在 swiftlint-linux/swiftlint)。

新增這些之後,可以在清單檔案中變數:

json { "schemaVersion": "1.0", "artifacts": { "swiftlint": { "version": "0.47.0", # The version of SwiftLint being used "type": "executable", "variants": [ { "path": "swiftlint-macos/swiftlint", "supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"] }, { "path": "swiftlint-linux/swiftlint", "supportedTriples": ["x86_64-unknown-linux-gnu"] }, ] }, } }

為此,需要為每個變數指定二進位制檔案的相對路徑(從工件包目錄的根目錄)和支援的三元組。 如果您不熟悉 目標三元組,它們是一種選擇構建二進位制檔案的架構的方法。 請注意,這不是 主機(構建可執行檔案的機器)的體系結構,而是 目標 機器(應該執行所述可執行檔案的機器)。

這些三元組具有以下格式: ---- 並非所有欄位都是必需的,如果其中一個欄位未知並且要使用預設值,則可以省略或替換為 unknown 關鍵字。

可執行檔案的架構切片可以通過執行 file 找到,這將列印捆綁的任何切片的供應商、系統和架構。 在這種情況下,為這兩個命令執行它會顯示:

swiftlint-macos/swiftlint

swiftlint: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64] swiftlint (for architecture x86_64): Mach-O 64-bit executable x86_64 swiftlint (for architecture arm64): Mach-O 64-bit executable arm64

swiftlint-linux/swiftlint

-> file swiftlint swiftlint: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, with debug_info, not stripped 這帶來了上面顯示的 macOS 支援的兩個三元組(x86_64-apple-macosxarm64-apple-macosx)和 Linux 支援的一個三元組(x86_64-unknown-linux-gnu)。

XCFrameworks 類似,工件包也可以通過使用 binaryTarget 包含在 Swift 包中。

結論

簡而言之,我們可以總結 2022 年如何在 Swift 包中使用二進位制檔案的最佳實踐,如下所示:

  1. 如果你需要為你的 iOS/macOS 專案新增預編譯庫或可執行檔案,您應該使用 XCFramework,併為每個用例(iOS 裝置、macOS 裝置和 iOS 模擬器)包含單獨的二進位制檔案。
  2. 如果你需要建立一個外掛並執行一個可執行檔案,你應該將其嵌入為一個工件包,其中包含適用於不同支援架構的二進位制檔案。

我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿