在前面的模組化系列中,我們整理了從邊界、相依方向到分層結構的基本原則。現在該實際套用到 iOS 專案了。
在 iOS 中拆分模組,實際上有兩種工具:Xcode framework target,以及 Swift Package(SPM,Swift Package Manager)。
先說結論:以 2026 年來看,若要開始新的模組化,SPM 是預設選擇。只要使用資料夾和 Package.swift,就能建立模組,不會產生專案檔(pbxproj)衝突。
本文將整理如何使用 SPM 建立本機模組、它與 framework target 的差異,以及選擇 static 或 dynamic 連結方式的基準。
為什麼不是 framework target,而是 SPM?
也可以在 Xcode 中透過 File → New → Target → Framework 建立模組。長久以來,這一直是標準做法。
問題在於這種方式留下的痕跡。每新增一個 target,project.pbxproj 就會變更數十行;如果兩位團隊成員同時修改 target,就會發生合併衝突。
SPM 本機套件則不同。
| 比較 | Framework target | SPM 本機套件 |
|---|---|---|
| 模組定義位置 | project.pbxproj | Package.swift(宣告式程式碼) |
| 合併衝突 | 頻繁 | 少見(檔案是人類可讀的程式碼) |
| 新增模組的成本 | 操作 Xcode GUI | 資料夾+新增幾行程式碼 |
| 細緻的建置設定 | 自由 | 有限 |
模組越多,「新增一個模組的成本」越會決定整體結構。因為新增模組若令人畏懼,就不會有人願意拆分。
除非需要非常細緻地控制建置設定,否則本機 SPM 套件在管理成本上更具優勢。
如何建立本機 SPM 套件?
在 App 專案旁建立一個資料夾,再放入 Package.swift 就完成了。我們直接套用上一篇文章的分層結構。
// Modules/Package.swift
let package = Package(
name: "Modules",
products: [
.library(name: "FeatureSearch", targets: ["FeatureSearch"]),
.library(name: "CoreNetwork", targets: ["CoreNetwork"]),
],
targets: [
.target(name: "FeatureSearch", dependencies: ["CoreNetwork"]),
.target(name: "CoreNetwork"),
.testTarget(name: "FeatureSearchTests", dependencies: ["FeatureSearch"]),
]
)
將這個套件拖曳到 Xcode 專案中,並將程式庫連結到 App target,接著就能使用 import FeatureSearch。
重點是,相依關係會以程式碼明確寫在 dependencies 陣列中。如果 FeatureSearch 不應使用 CoreNetwork,就從陣列移除;偷偷 import 時會產生編譯錯誤。
上一篇文章所說的「由編譯器強制邊界」,就是這樣建立的。
除了像上述方式在一個套件中放入多個 target,也可以為每個模組各放一個套件。模組少時,單一套件多 target 較易管理;達到數十個規模後,許多團隊會依網域拆分套件。
static 和 dynamic,該選哪一個?
宣告 .library 時可以指定連結方式。預設值是由 Xcode 自動決定的 automatic,而且大多會以 static 處理。
- static:模組程式碼會複製並合併到 App 二進位檔中。App 啟動時不需額外載入
- dynamic:模組以獨立的 framework 檔案存在,並在執行時載入
選擇基準如下。
| 情境 | 選擇 |
|---|---|
| 一般功能・核心模組 | static(維持預設值) |
| App、Widget 與 Extension 共用相同模組 | dynamic(消除二進位檔重複) |
| App 啟動時間敏感,但有數十個 dynamic | 考慮整合為 static |
dynamic framework 越多,App 啟動時的載入成本越高。這也是 Apple 在 WWDC session 中持續建議的做法,因此除非有特殊理由,維持 automatic(實際上是 static)通常最穩妥。
什麼時候只靠 SPM 會不夠?
從 SPM 模組化開始並順利使用後,團隊變大時仍會遇到一些限制。
- App target 本身的設定(簽署、scheme、建置設定)仍留在 pbxproj 中,因此仍可能發生衝突
- 模組達到數十個後,管理 Package.swift 與統一模組範本會變得麻煩
- 希望在組織層級共用建置快取
這時會出現的工具就是 Tuist。這將是系列最後一篇的主題。
面試時會這樣問
Q. 在 iOS 模組化中,為什麼要使用 SPM 本機套件?
模組定義不是由 pbxproj 管理,而是以宣告式 Package.swift 程式碼管理,因此能減少合併衝突,也降低新增模組的成本。此外,target 間的相依關係會明確寫在 dependencies 中,未獲允許的 import 會以編譯錯誤阻擋,讓編譯器強制執行模組邊界。
Q. 請說明 static library 與 dynamic framework 的差異及選擇基準。
static 會在連結時複製到 App 二進位檔,因此執行時沒有載入成本;dynamic 則以獨立檔案存在,並在執行時載入。預設使用 static;當 App、Extension 與 Widget 共用相同程式碼、需要減少二進位檔重複時,選擇 dynamic。
下一篇將介紹許多團隊開始模組化的真正契機:Xcode 建置速度。
我們會整理建置變慢的原因、模組化能改善哪些地方及改善多少,以及如何用數字衡量改善效果。

![[模組化 #4] 使用 SPM 開始 iOS 模組化(本機套件、static、dynamic 完整整理) 封面圖](/assets/images/posts/2dfa8b93-209b-47ab-8ad8-2745ec0ab314/1.jpg)