首頁 > 軟體

Swift Package 技巧及混編相容問題詳解

2023-09-06 14:00:13

建立 Package

mkdir somePath
cd somePath
swift package init (--type library/executable/empty/system module)

其中,type 的四種型別分別對應:

  • library: 庫(預設)
  • executable: 可執行檔案
  • empty: 空專案
  • system module: 系統模板專案

一般情況下預設即可

建立 package 之後,還可以使用 swift package generate-xcodeproj 建立一個Xcode專案來編譯和偵錯程式碼

使用 Package

在 Xcode 選單欄中,選擇 file -> add packages

image-20220109204812598.png

可以指定 package 的版本規則

維護 Package

版本控制

在 package 路徑下,使用 git init 來建立一個倉庫。之後上傳至遠端即可

本地偵錯修改

對於通過 cocoapods 引入的庫,如果想在本地修改該庫並提交的話,需要在 PodFile 裡面把路徑改成原生的,然後再 pod install 一下,比較麻煩

但是修改 swift package 引入的庫就很方便了,直接把 package 的檔案目錄拖到工程目錄下即可。修改好提交到遠端之後,右鍵 delete -> remove reference (move to trash 會刪掉本地檔案)

然後 file -> packages -> update to latest,即可更新到最新的版本

Package 之間的依賴

在每個 swift package 的目錄下都有個 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: "some name",
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "some name",
            targets: ["some name"]),
    ],
    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: "some name",
            dependencies: []),
        .testTarget(
            name: "some name",
            dependencies: ["some name"]),
    ]
)

其中的欄位分別表示:

  • name: 庫/專案名字
  • products: 庫/專案生成的東西,可以是 library 或者 executable. 同一個庫/專案可以生成多個 library 或者 executable
  • dependencies: 此庫/專案所依賴的庫,及依賴庫的 URL 和版本等資訊。如果依賴本地庫的話,可以新增 .package(path:"local path")
  • targets: 庫/專案生成的目標

相較於 cocoapods,swift package 還是更加方便點的,而且是蘋果自家的產品。但是目前很多三方庫都在 cocoapods 上,swift package 的大面積普及還需要一段時間

混編問題

target 拆分

Apple 官方檔案裡說:

Targets can contain Swift, Objective-C/C++, or C/C++ code, but an individual target can’t mix Swift with C-family languages. For example, a Swift package can have two targets, one that contains Objective-C, Objective-C++, and C code, and a second one that contains Swift code.

也就是說,SPM 是支援 objc 以及 C 系程式碼的。但是同一個 target 裡面只能有一種語言,Swift 檔案不能和 objc 檔案放到一個 target 裡。

如果我們想要在一個 SPM 倉庫裡面同時放置兩種語言的程式碼的話,就需要將倉庫拆分為兩個 target:

let package = Package(
    name: "MyModule",
    platforms: [.iOS(.v11),
                .macOS(.v11)],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "MyModule",
            targets: ["MyModule", "MyModule_Objc"])
    ],
    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: "MyModule_Objc",
            dependencies: [],
            publicHeadersPath: "include",
            cSettings: [.headerSearchPath(".")],
            cxxSettings: [.headerSearchPath(".")]
        ),
        .target(
            name: "MyModule",
            dependencies: ["MyModule_Objc"],
            path: "Sources/MyModule",
            swiftSettings: [.define("SPM_MODE")]
        ),
        .testTarget(
            name: "MyModuleTests",
            dependencies: ["MyModule"]),
    ]
)

其中,倉庫檔案結構應該為:

- MyModule
  |
  |-README.md
  |
  |-Package.swift
  |
  |-Sources
    |
    |-MyModule
    | |
    | |- MyPackage.swift  
    |
    |-MyModule_Objc
      |
      |-Include
      | |
      | |- MyObjCClass.h
      |
      |- MyObjCClass.m

Sources 資料夾拆分為兩個子資料夾,分別是兩個 target 的路徑。Swift target 依賴 objc 的 target, objc 的 target 可以設定公開 header 的路徑。

這樣,objc 以及 C/C++ 這些 C 系的檔案都放在 MyModule_Objc 資料夾下,Swift 檔案放到 MyModule 資料夾下,就可以在同一個 SPM 倉庫下實現混編了。

參照以及和 cocoapods 的相容問題

在上面的 package 設定裡面可以看到一行預處理宏的定義:

swiftSettings: [.define("SPM_MODE")]

這個設定是用來標識當前庫是通過 SPM 方式引入的。根據上文,SPM 模式下,當前庫的 Swift 和 objc 是兩個 target。因此,同一個庫的 Swift 檔案想要呼叫 objc 檔案的話,必須引入 objc 的 target:

import MyModule_Objc
// your code...

但是,我們的庫一般情況下還是要支援 cocoapods 的。在 cocoapods 模式下,同一個倉庫的 Swift 和 objc 檔案是不需要拆分為兩個 target 的,pod 是通過橋接檔案等來實現兩者之間相互呼叫的。

那麼問題就來了,當使用 cocoapods 引入當前庫時,import MyModule_Objc 是肯定會報錯的,因為 pod 並不會生成 MyModule_Objc

這時候,我們在 package 檔案裡預先定義的宏就派上用場了:

#if SPM_MODE
import FoundationX_Objc
#endif

只有在 SPM 模式下,才會 import MyModule_Objc,這樣就解決了與 cocoapods 的相容問題。

以上就是Swift Package 技巧及混編相容問題詳解的詳細內容,更多關於Swift Package混編相容的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com