iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
Mobile Development

在 iOS 開發路上的大小事2系列 第 21

【在 iOS 開發路上的大小事2-Day21】如何將自己寫的套件上傳到 Swift Package

  • 分享至 

  • xImage
  •  

前言

先前有寫過「如何將自己寫的套件上傳到 CocoaPods
剛好 Swift Package Manager 也是近年 iOS App 開始使用的第三方套件管理工具
所以就來研究一下,該如何製作一個 Swift Package

建立 Git Repo

就一般的建立 git repo,嗯對
README.md、.gitignore 都不用勾,只需要勾 LICENSE
因為後面在建立 Swift Package 的時候,會自己建立

建立一個 Swift Package

建立 Swift Package 有兩種方法

  1. Terminal
  2. Xcode

Terminal

首先,先在 Terminal cd 到要建立 Package 的目錄,
像是我要在桌面建立,那我就是 cd 到桌面

mkdir <你幫 Package 取的名稱> # 這一步可透過手動建立資料夾來替代
cd <你幫 Package 取的名稱>
swift package init --type <這個 Package 建立的類型>

swift package init --type 的 type 一共有四種類型

  1. empty -> Sources 資料夾內什麼都沒有,Package.swift 需手動補齊,無法編譯
  2. library -> 建立 iOS SDK 的話,用這個 (推薦)
  3. executable
  4. system-module

empty


▲ 使用 type empty 建立的資料夾結構

▲ 使用 type empty 建立的 Xcode 結構

library


▲ 使用 type library 建立的資料夾結構

▲ 使用 type library 建立的 Xcode 結構

executable


▲ 使用 type executable 建立的資料夾結構

▲ 使用 type executable 建立的 Xcode 結構

system-module


▲ 使用 type system-module 建立的資料夾結構

▲ 使用 type system-module 建立的 Xcode 結構

Xcode

從 Xcode 新增有兩種方法,兩種方法建立出來的 Package type 都是 library

  1. Files -> New -> Package

  1. Create a new Xcode project -> Multiplatform -> Swift Package

幫這個 Package 取名稱,預設名稱為 MyLibrary

按下 create 就會新增一個 Swift Package 了

撰寫 Sources Code

使用 type library 或是 type executable 建立的 Swift Package
在 Sources 資料夾內都有預先建立好一個 swift 檔來告訴我們原始碼要放的位置
(type empty 也可以透過手動補齊的方式,變成 type library)

type library 會在 Sources 資料夾內建立一個跟 Package 同名的資料夾,裡面會有一個跟 Package 同名的 swift 檔

以我的為例就是 MyFirstSwiftPackageLibrary.swift
裡面已經有先宣告一個與 Package 同名的 struct

public struct MyFirstSwiftPackageLibrary {
    
    /// 這個變數的 Access Level
    /// - getter -> public
    /// - setter -> private
    /// - getter 跟 setter 同時指定時,只需要注明 setter 為 (set)
    public private(set) var text = "Hello, World!"

    /// 初始化
    public init() {
    }
}

那我們來改寫一下,讓他包含我們要的 Function

public struct MyFirstSwiftPackageLibrary {
    
    /// 這個變數的 Access Level
    /// - getter -> public
    /// - setter -> private
    /// - getter 跟 setter 同時指定時,只需要注明 setter 為 (set)
    public private(set) var text = "Hello, World!"

    /// 初始化
    public init() {
    }
    
    public static func sayHello() {
        print("MyFirstSwiftPackageLibrary say Hello!")
    }
    
    public static func inputAndOutput(input: String?) {
        print("MyFirstSwiftPackageLibrary input \(String(describing: input)), and I output \(String(describing: input))")
    }
}

這邊我們新增了 func sayHello()、func inputAndOutput(input: String?) 來做示範

撰寫 Package.swift

預設會長得像下面這樣

// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MyFirstSwiftPackageLibrary",
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "MyFirstSwiftPackageLibrary",
            targets: ["MyFirstSwiftPackageLibrary"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.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 this package depends on.
        .target(
            name: "MyFirstSwiftPackageLibrary",
            dependencies: []),
        .testTarget(
            name: "MyFirstSwiftPackageLibraryTests",
            dependencies: ["MyFirstSwiftPackageLibrary"]),
    ]
)

// swift-tools-version:5.5

我們看到第一行 // swift-tools-version:5.5
這邊的 5.5 指的是編譯時最低支援的 Swift 版本
後續有個 swiftLanguageVersions 參數可以指定相容的 Swift 版本
兩者之間必須以 // swift-tools-version:5.5 定義的版本為主

Example 1:
// swift-tools-version:5.5
swiftLanguageVersions: [.v5, .version(5.5)]
↑ 這樣可以 Build Succeeded,且無報錯

Example 2:
// swift-tools-version:5.5
swiftLanguageVersions: [.version(5.5.1)]
↑ 這樣可以 Build Succeeded,但會報錯
  因為上方定義的 swift-tools 版本為 5.5
  但這邊指定相容的 swift 版本為 5.5.1
  swift version 5.5 不相容 swift version 5.5.1

Package

因完整參數較多,這邊主要以常用參數來介紹,完整參數請查看下面官方文件

完整 Package 參數-Apple Developer Documentation

如果有寫錯的話,執行鍵旁邊的 Scheme 會消失,不讓你編譯!
這算是一種物理 debug???

name -> 這個 Package 的名稱

platforms -> 這個 package 所支援的平台

products -> 這個 Package 要對外呈現的方式

dependencies -> 這個 Package 所依賴的其他套件

targets -> 這個 Package 的 target List

swiftLanguageVersions -> 這個 Package 所相容的 Swift 版本

cLanguageStandard -> 這個 Package 所使用的 C 語言標準

cxxLanguageStandard -> 這個 Package 所使用的 C++ 語言標準

platforms

這個 package 所支援的平台,像是 iOS、macOS、watchOS、tvOS、DriverKit、Linux

寫法如下

platforms: [
    .iOS(.v14),
    .macOS(.v12),
    .tvOS(.v14),
    .watchOS(.v7),
    .driverKit(.v21)
]

products

這個 Package 要對外呈現的方式,有三種

  1. Library -> 最常見的
  2. Executable -> Package type 為 executable
  3. Plugin -> 插件,像是 Apple 的 SwiftDocCPlugin

寫法如下

products: [
    .library(
        name: "MyFirstSwiftPackageLibrary",
        targets: ["MyFirstSwiftPackageLibrary"]),
    .executable(
        name: "MyFirstSwiftPackageLibrary",
        targets: ["MyFirstSwiftPackageLibrary"]),
    .plugin(
        name: "MyFirstSwiftPackageLibrary",
        targets: ["MyFirstSwiftPackageLibrary"]),
]

dependencies

這個 Package 所依賴的其他套件

這邊以之前所建立的 MyFirstPodLib 為例

寫法如下

dependencies: [
    /// 指定版本
    .package(url: "https://github.com/leoho0722/MyFirstPodLib.git", from: "0.0.4"),
    
    /// 指定版本區間
    .package(url: "https://github.com/leoho0722/MyFirstPodLib.git", "0.0.1" ... "0.0.4"),
    
    /// 指定分支
    .package(url: "https://github.com/leoho0722/MyFirstPodLib.git", branch: "master"),
    
    /// 指定 Commit
    .package(url: "https://github.com/leoho0722/MyFirstPodLib.git", revision: "172bc5cc5b92339f48346d549fc9212d50b1e68e"),
    
    /// 本地路徑
    .package(path: "../MyFirstPodLib"),
]

targets

這個 Package 的 target List

因完整參數較多,這邊主要以常用參數來介紹,完整參數請查看下面官方文件

完整 Target 參數-Apple Developer Documentation

name -> Target 名稱

path -> Target 路徑,相依於 Package Root
        預設為 [PackageRoot]/Sources/[TargetName]

exclude -> 不想包含在 Target 內的檔案路徑

sources -> 在 Target 內的原始碼檔案路徑
           預設 TargetName 底下都是原始碼檔案,採用遞歸搜尋
           
resources -> 在 Target 中的 Resources Files
             像是 `Image Files`、`Xib Files`、`Text Files`、`Folder` 等

publicHeadersPath -> C 語言的公開標頭檔路徑

cSettings -> C 語言檔案編譯時的 Build Settings

cxxSettings -> C 語言檔案編譯時的 Build Settings

swiftSettings -> Swift 檔案編譯時的 Build Settings

linkerSettings -> Package 內有用到的系統內建 Framework,要寫在這裡
resources

在 Target 中的 Resources Files

Swift tools version 5.3 起支援將 Resources Files 一同打包到 Swift Package 內

下圖是不需要在 Package.swift 內標示的 Resources file types
因為 Xcode 能夠自動辨識其類型


▲ 圖截自 WWDC 2020-Swift packages: Resources and localization

下圖是需要在 Package.swift 內標示的 Resources file types
因為 Xcode 不能夠自動辨識其用途


▲ 圖截自 WWDC 2020-Swift packages: Resources and localization

其中分為 func process() 與 func copy()

/// 會根據所使用的平台,對 Resouces Files 進行相對應的優化
/// 大部分的 Resources Files 都應該使用 process
func process() # 推薦

/// Xcode 無法辨識用途的檔案、不根據平台進行優化,只進行拷貝
/// 如下圖的 Game Data 資料夾
func copy()


▲ 圖截自 WWDC 2020-Swift packages: Resources and localization

linkerSettings

Package 內有用到的系統內建 Framework,要寫在這裡

寫法如下

linkerSettings: [
    .linkedFramework("UIKit", .when(platforms: [.iOS])),
    .linkedFramework("AppKit", .when(platforms: [.macOS]))
]

swiftLanguageVersions

這個 Package 所相容的 Swift 版本

這邊所定義的 Swift 版本不能大於最上方宣告的 // swift-tools-version:5.5

範例請看上方的 // swift-tools-version:5.5

寫法如下

/// 單一版本
swiftLanguageVersions: [.v5]

/// 多個版本
swiftLanguageVersions: [.v4_2, .v5]

/// 指定版本
swiftLanguageVersions: [.version("5.5")]

上傳

就一般的 git push、git tag,嗯對

這部分跟 CocoaPods 蠻不一樣的

實際應用

在一個專案內 Add Package,貼上 Git Repo 網址

在要用的檔案 import MyFirstSwiftPackageLibrary

執行結果

新增套件的流程

這邊也整理一下新增套件的流程

步驟0:先建立一個要用來存放套件的 git repo

步驟1:在本地建立一個用來存放套件的資料夾

步驟2:用 Terminal 切到剛剛建立用來存放套件的資料夾目錄底下

步驟3:在 Terminal 輸入 swift package init --type <這個 Package 建立的類型> 
      or Xcode File -> New -> Add Package

步驟4:打開 Package.swift (步驟3 用 Terminal 才需要)

步驟5:將 Source Code、圖片、Color Set 等資源放入 Sources/<Package 名稱> 資料夾內

步驟6:撰寫 Package.swift (看 Scheme 有沒有出現就知道有沒有寫錯了)

步驟7:Command+B 編譯,看是否成功編譯以及無錯誤
      (target 裡面的 sources 路徑定義錯誤,會有警告訊息,要注意!!!!)

步驟8:將 Source Code 上傳到 git repo

步驟9:為剛剛上傳到 git repo 的套件新增 git tag

步驟10:測試是否可以在其他專案成功安裝

步驟11:(optional):修改 README.md

更新套件的流程

有推出,就會有更新

所以這裡就要來講説,當要更新套件的時候,要怎麼來更新

更新流程如下

步驟1:更新 Source Code、圖片、Color Set 等檔案

步驟2:將更新過後的套件 git 到 git repo

步驟3:git tag <新版號>

步驟4:git push --tags,將新版號 push 到 git repo

步驟5:測試是否可以在專案內成功抓到新版本的套件

步驟6 (optional):修改 README.md

參考資料

  1. https://juejin.cn/post/6871489791213436941
  2. https://www.appcoda.com.tw/swift-package-xcode/
  3. https://developer.apple.com/documentation/packagedescription/package

上一篇
【在 iOS 開發路上的大小事2-Day20】如何將自己寫的套件上傳到 CocoaPods
下一篇
【在 iOS 開發路上的大小事2-Day22】如果當 Xcode 與手上裝置的 iOS 系統不相容的時候該怎麼辦呢?
系列文
在 iOS 開發路上的大小事230
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言