使用 Swift 的 Monorepo:为 iOS 和 macOS 项目组织代码
在现代 iOS 和 macOS 开发中,代码管理策略的选择直接影响开发速度、重构质量和团队协作的便利性。传统的多仓库方法(polyrepo)常常导致依赖重复、版本同步困难以及代码库碎片化。替代方案是 monorepo——一个为项目的所有模块、库和应用程序提供的统一存储库。在本文中,我们将以 Swift 和 Apple 生态系统为例,探讨如何组织 monorepo、使用哪些工具以及需要注意哪些陷阱。
什么是 Monorepo,为什么 Swift 项目需要它
Monorepo 是一种版本管理策略,其中项目的所有代码(多个应用程序、库、配置)都存储在一个 Git 仓库中。在 Swift 的上下文中,这意味着您可以在一个仓库中拥有:
- iOS 应用程序(例如,
MyApp) - macOS 应用程序(
MyMacApp) - 通用模型库(
CoreModels) - 网络层(
Networking) - UI 组件(
UIComponents) - 测试和 CI 脚本
Monorepo 对 Swift 项目的优势:
- 统一的依赖版本——所有模块使用相同版本的库(例如,
Alamofire或SwiftUI),从而消除冲突。 - 原子提交——对库 API 的更改及其在应用程序中的使用通过一次提交完成,从而简化代码审查和回滚。
- 简化重构——您可以重命名模块中的公共方法,并立即在同一仓库中修复所有调用位置。
- 共享 CI/CD——用于构建和测试所有组件的单一流水线。
在 Swift 中使用 Monorepo 的工具
在 Apple 生态系统中,有两个主要工具用于组织 monorepo:Swift Package Manager (SPM) 和 Tuist。让我们看看它们的特点。
Swift Package Manager (SPM)
SPM 是一个内置的包管理器,非常适合 monorepo。您通过 Package.swift 定义模块,Xcode 会自动解析依赖关系。结构示例:
MyMonorepo/├── Package.swift├── Sources/│ ├── CoreModels/│ │ └── Models.swift│ ├── Networking/│ │ └── APIClient.swift│ └── UIComponents/│ └── ButtonView.swift├── Apps/│ ├── MyApp/│ │ └── (iOS проект)│ └── MyMacApp/│ └── (macOS проект)└── Tests/ ├── CoreModelsTests/ └── NetworkingTests/此类 monorepo 的 Package.swift 示例:
// swift-tools-version:5.9import PackageDescription
let package = Package( name: "MyMonorepo", platforms: [ .iOS(.v16), .macOS(.v13) ], products: [ .library(name: "CoreModels", targets: ["CoreModels"]), .library(name: "Networking", targets: ["Networking"]), .library(name: "UIComponents", targets: ["UIComponents"]), ], dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0") ], targets: [ .target(name: "CoreModels"), .target( name: "Networking", dependencies: ["CoreModels", "Alamofire"] ), .target( name: "UIComponents", dependencies: ["CoreModels"] ), .testTarget(name: "CoreModelsTests", dependencies: ["CoreModels"]), .testTarget(name: "NetworkingTests", dependencies: ["Networking"]), ])