XCFramework和Swift Package實用學習筆記
一點背景
- 蘋果在2019的WWDC上發佈了Xcode 11的新功能,XCFramework格式的二進制框架
- 剛開始一個框架不能支持多個平台,比如同時支持iOS和macOS,只能針對不同平台發布不同的框架
- WWDC 2020發布了Xcode 12開始的新功能,Swift Package可以用來發布binary的XCFramework,而非之前的只能用來發布開源代碼
- 以上,使得我們可以用Swift Package來管理支持不同平台的binary框架
一步步創建XCFramework
我们试着做一个跨平台的框架,代码本身并不重要,主要是为了展示建立XCFramework的过程。打开Xcode,我們先來建立一個Framework项目:
我們的代碼擴展UIColor
,提供一些類似膠片和古畫裡,有點褪色的色彩定義,鑑於此,我們起名為PaleInk。建立一個iOS的framework,PaleInk-iOS,並增加一个Swift文件UIColor+Ink.swift。先給UIColor
加兩個convenient initialiser,可以用RGB值以及繪圖軟件常用的16進制值來創建UIColor
實例:
import UIKit
public extension UIColor {
// Create a UIColor from RGB
convenience init(red: Int, green: Int, blue: Int, a: CGFloat = 1.0) {
self.init(
red: CGFloat(red) / 255.0,
green: CGFloat(green) / 255.0,
blue: CGFloat(blue) / 255.0,
alpha: a
)
}
// Create a UIColor from a hex value (E.g 0x000000)
convenience init(hex: Int, a: CGFloat = 1.0) {
self.init(
red: (hex >> 16) & 0xFF,
green: (hex >> 8) & 0xFF,
blue: hex & 0xFF,
a: a
)
}
}
同樣的步驟,創建一個macOS的framework,增加同樣的代碼:
import AppKit
public extension NSColor {
convenience init(red: Int, green: Int, blue: Int, a: CGFloat = 1.0) {
self.init(
calibratedRed: CGFloat(red) / 255.0,
green: CGFloat(green) / 255.0,
blue: CGFloat(blue) / 255.0,
alpha: a
)
}
// Create a UIColor from a hex value (E.g 0x000000)
convenience init(hex: Int, a: CGFloat = 1.0) {
self.init(
red: (hex >> 16) & 0xFF,
green: (hex >> 8) & 0xFF,
blue: hex & 0xFF,
a: a
)
}
}
我們把兩個不同平台的Framework放在同一個目錄裡:
接下來我們來打包這兩個Framework(archive),保存默認Archive的目錄是/Users/username/Library/Developer/Xcode/Archives,也可以自己定製。我們自己在上圖同樣的目錄下建立一個新的Archive目录。
打包Archive可以用XCode完成,由于工作中许多事情是CI用命令行来做,我們這裡也採用命令行的方式。先來到PaleInk-iOS目錄:
hzxu$ pwd
/Users/hzxu/Projects/PaleInk/PaleInk-iOS
然後執行命令:
hzxu$ xcodebuild archive \
> -scheme PaleInk-iOS \
> -destination "generic/platform=iOS" \
> -archivePath ../Archive/PaleInk-iOS \
> SKIP_INSTALL=NO \
> BUILD_LIBRARY_FOR_DISTRIBUTION=YES
輸入了這一長串命令後,Xcode會給你大量的輸出,當然,最重要的是這最後的結果:
- ARCHIVE SUCCEEDED **
命令行裡這兩個參數值得注意:
- destination:指定目標設備和平台
- SKIP_INSTALL:是否將編譯的目標(built target)安裝到存檔(archive)裡,這裏我們將其設為NO,意思是生成的.archive文档里会包含我们编译的框架;设为YES的情况,比如编译静态连接库lib.a這類,之後會連接到其他的target或bundle上。
- BUILD_LIBRARY_FOR_DISTRIBUTION:這個參數確保我們的框架適合發布,具體來講,這個參數為YES時,Swift編譯器會生成必要的.swiftinterface文件,新版本編譯器可以通過這個文件來使用舊版本編譯創建的庫。
現在我們再創建一個模擬器適用的框架:
hzxu$ xcodebuild archive \
> -scheme PaleInk-iOS \
> -destination "generic/platform=iOS Simulator" \
> -archivePath ../Archive/PaleInk-Sim \
> SKIP_INSTALL=NO \
> BUILD_LIBRARY_FOR_DISTRIBUTION=YES
我們再為macOS創建同樣的框架:
hzxu$ xcodebuild archive \
> -scheme PaleInk-macOS \
> -destination "generic/platform=OS X" \
> -archivePath ../Archive/PaleInk-macOS \
> SKIP_INSTALL=NO \
> BUILD_LIBRARY_FOR_DISTRIBUTION=YES
成功後,Archive目錄裡就有我們創建的三個.xcarchive存檔文件了:
好奇的話我們可以看看.xcarchive文件裡有那些東西:
最後我們來創建XCFramework,先來到Archive目錄:
hzxu$ pwd
/Users/hzxu/Projects/PaleInk/Archive
創建XCFramework需要提供每個framework的路徑,該路徑為.xcarchive文件包裡面Products/Library/Frameworks目錄下相應的.framework文件:
hzxu$ xcodebuild -create-xcframework \
> -framework ./PaleInk-iOS.xcarchive/Products/Library/Frameworks/PaleInk_iOS.framework \
> -framework ./PaleInk-Sim.xcarchive/Products/Library/Frameworks/PaleInk_iOS.framework \
> -framework ./PaleInk-macOS.xcarchive/Products/Library/Frameworks/PaleInk_macOS.framework \
> -output ./PaleInk.xcframework
成功後我們會看到:
xcframework successfully written out to: /Users/hzxu/Projects/PaleInk/Archive/PaleInk.xcframework
我們創建了一個包含多個framework的XCFramework框架,而多個framework分別對應iOS, macOS以及模擬器。在此基礎上,我們再來創建一個Swift package。
一步步創建Swift Package
Swift Package可以放在遠程,比如GitHub,因為package裡可以包含編譯好的二進制Binary Framework,為了不影響下載時間,二進制的binary framework可以以zip壓縮包的形式放在其他服務器上,而package裡只包含其URL。正如Cocoapod一樣,Swift Package也可以放在本地機器上,這種情況就需要package裡也包含二進制的binary framework了。
在XCode裡創建一個Swift Package,其一個簡單易懂的名字,比如PaleInkLocal。
默認情況下,你會看到XCode為你自動生成的一些文件:
如果你不打算寫測試,也沒有什麼額外的要寫入PaleInkLocal.swift裡的,可以把不需要的文件刪除,最後把之前打包好的PaleInk.xcframework拖拽到Sources目錄下。注意:拖拽會把原本的PaleInk.xcframework移動到Swift Package相應的目錄下,而非複製。最後的效果如下:
我們還需要對Package.swift
文件進行修改:
import PackageDescription
let package = Package(
name: "PaleInkLocal",
products: [
.library(
name: "PaleInk",
targets: ["PaleInk"]),
],
dependencies: [],
targets: [
.binaryTarget(name: "PaleInk", path: "./Sources/PaleInk.xcframework")
]
)
接下來我們創建一個新項目來使用這個Package,這裡我們其名為PaleInkDemo,並把剛剛創建的Package目錄整個拖到PaleInkDemo項目裡,效果如下:
接下來,在項目設置的General → Frameworks, Libraries, and Embedded Content那裡增加PaleInk:
在項目裡,我們導入PaleInk_iOS來使用剛剛創建的這個Package:
import PaleInk_iOS
我們簡單的做了一個Demo,在一個UITableView裡展示所有的色彩:
接下來,我們進一步看看怎麼把這個Package從本地弄到遠程。
遠程Swift Package
我們先來到之前創建xcframework的Archive目錄,把xcframework壓縮成zip文件:
hzxu$ zip -r PaleInk.xcframework.zip PaleInk.xcframework
之後我們建立一個新的Swift Package,這裡就叫PaleInk,按照之前PaleInkLocal的初試設置清理不需要的文件後,Package.swift文件內容修改如下:
import PackageDescription
let package = Package(
name: "PaleInk",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "PaleInk",
targets: ["PaleInk"]),
],
dependencies: [],
targets: [
.binaryTarget(name: "PaleInk", url: "https://www.haozhexu.me/SwiftPackages/PaleInk.xcframework.zip", checksum: "7c29c4a683a8102f2a8c11227f44bac4452dd0a38dd2b4ed34e1f808c6058080")
]
)
這裏面targets的binaryTarget,我們把xcframework放在了遠程,並且用蘋果提供的工具來計算checksum:
hzxu$ swift package compute-checksum ../Archive/PaleInk.xcframework.zip
7c29c4a683a8102f2a8c11227f44bac4452dd0a38dd2b4ed34e1f808c6058080
接下來我們把這個Swift Package傳到GitHub,還是在打開剛剛建立的PaleInk這個Swift Package的狀態下,點擊Source Control → New Git Repositories…來為這個Swift Package建立一個新的Git Repo並且提交(commit)所有文件。
有了本地的Git Repo,我們把它傳到Remote,打開Source Control (Cmd+2),移動鼠標到PaleInk上點右鍵,點New “PaleInk” Remote…
XCode會讓你選擇已有的或者新建一個Git帳號:
填寫必要的信息後,點擊Create,接下来我们把package推到remote,右鍵點擊Package名,菜單裡點Tag “main”:
在接下來的窗口中給Package指定一個tag,我們這裡就用它的版本:
最後我們把本地的改變給推到遠程:
最後我們來測試一下,打開剛才創建的PaleInkDemo,把之前添加的本地Package刪掉,注意,點Delete後XCode會問你是要把這個文件刪掉,還是只刪除文件的引用(Remove Reference),我們選擇後者,這樣就不會把原本在硬盤上的PaleInkLocal一起刪掉了。
我們通過Swift Package把PaleInk再重新加回來:
填寫必要的信息:
最終PaleInk被加入到了項目裡:
Package在項目裡的目錄結構如下:
延伸學習
至此,一個簡單的xcframework和Swift Package的創建就完成了,我們做的只能算是理想狀態,實際工作中一般會有各種複雜的情況。要想深入了解,最好的資源是蘋果的WWDC視頻以及文檔:
Binary Frameworks in Swift(Swift二進制框架): https://developer.apple.com/videos/play/wwdc2019/416/
Distribute binary frameworks as Swift packages(以Swift Package方式發布二進制框架): https://developer.apple.com/videos/play/wwdc2020/10147/
Create an XCFramework(創建XCFramework): https://help.apple.com/xcode/mac/current/#/dev544efab96
PaleInkDemo可以在GitHub上下載:https://github.com/haozhexu/PaleInkDemo