Монорепозиторий с Swift: организация кода для iOS и macOS проектов
В современной разработке под iOS и macOS выбор стратегии управления кодом напрямую влияет на скорость разработки, качество рефакторинга и удобство командной работы. Традиционный подход с множеством отдельных репозиториев (полирепозиторий) часто приводит к дублированию зависимостей, сложностям с синхронизацией версий и фрагментации кодовой базы. Альтернативой служит монорепозиторий (monorepo) — единое хранилище для всех модулей, библиотек и приложений проекта. В этой статье на примере Swift и экосистемы Apple мы разберем, как организовать монорепозиторий, какие инструменты использовать и какие подводные камни учесть.
Что такое монорепозиторий и зачем он нужен в Swift-проектах
Монорепозиторий — это стратегия управления версиями, при которой весь код проекта (несколько приложений, библиотек, конфигураций) хранится в одном Git-репозитории. В контексте Swift это означает, что вы можете иметь в одном репозитории:
- iOS-приложение (например,
MyApp) - macOS-приложение (
MyMacApp) - общую библиотеку моделей (
CoreModels) - сетевой слой (
Networking) - UI-компоненты (
UIComponents) - тесты и CI-скрипты
Преимущества монорепозитория для Swift-проектов:
- Единая версия зависимостей — все модули используют одну и ту же версию библиотеки (например,
AlamofireилиSwiftUI), что исключает конфликты. - Атомарные коммиты — изменения в API библиотеки и её использование в приложении фиксируются одним коммитом, что упрощает code review и откат.
- Упрощение рефакторинга — вы можете переименовать публичный метод в модуле и сразу исправить все места вызова в том же репозитории.
- Общие CI/CD — один пайплайн для сборки и тестирования всех компонентов.
Инструменты для работы с монорепозиторием в Swift
В экосистеме Apple есть два основных инструмента для организации монорепозитория: Swift Package Manager (SPM) и Tuist. Рассмотрим их особенности.
Swift Package Manager (SPM)
SPM — встроенный менеджер пакетов, который отлично подходит для монорепозиториев. Вы определяете модули через Package.swift, а Xcode автоматически разрешает зависимости. Пример структуры:
MyMonorepo/
├── Package.swift
├── Sources/
│ ├── CoreModels/
│ │ └── Models.swift
│ ├── Networking/
│ │ └── APIClient.swift
│ └── UIComponents/
│ └── ButtonView.swift
├── Apps/
│ ├── MyApp/
│ │ └── (iOS проект)
│ └── MyMacApp/
│ └── (macOS проект)
└── Tests/
├── CoreModelsTests/
└── NetworkingTests/
Пример Package.swift для такого монорепозитория:
// swift-tools-version:5.9
import 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"]),
]
)
Tuist
Tuist — более мощный инструмент для генерации Xcode-проектов. Он позволяет описать всю структуру монорепозитория в декларативном виде (на Swift) и автоматически генерировать .xcodeproj или .xcworkspace. Пример конфигурации Tuist:
// Project.swift (корневой)
import ProjectDescription
let project = Project(
name: "MyMonorepo",
targets: [
Target(
name: "CoreModels",
platform: .iOS,
product: .framework,
sources: ["Sources/CoreModels/**"]
),
Target(
name: "Networking",
platform: .iOS,
product: .framework,
sources: ["Sources/Networking/**"],
dependencies: [.target(name: "CoreModels")]
),
Target(
name: "MyApp",
platform: .iOS,
product: .app,
sources: ["Apps/MyApp/Sources/**"],
dependencies: [
.target(name: "CoreModels"),
.target(name: "Networking"),
.target(name: "UIComponents")
]
)
]
)
Tuist также поддерживает caching и binary caching, что ускоряет сборку больших монорепозиториев.
Организация кода: модульность и границы ответственности
Ключевой принцип монорепозитория — четкое разделение на модули. Каждый модуль должен иметь одну ответственность и минимальные зависимости. Рекомендуемая структура:
- Core — базовые типы, протоколы, расширения (не зависит от UI и сетевых библиотек).
- Models — бизнес-модели и DTO.
- Networking — работа с API, зависит от
CoreиModels. - UIComponents — переиспользуемые SwiftUI/UIKit компоненты.
- Features — функциональные модули (например,
AuthFeature,FeedFeature), которые собирают вместе модели, сетевой слой и UI. - App — тонкий слой, который собирает все модули вместе и инициализирует приложение.