Монорепозиторий с Swift: организация кода для начинающих
Когда вы начинаете разрабатывать приложения на Swift для iOS или macOS, рано или поздно перед вами встаёт вопрос: как организовать код, чтобы он не превратился в хаос? Особенно если у вас несколько проектов (например, iOS-приложение, watchOS-расширение и серверная часть на Vapor). Один из современных подходов — монорепозиторий (monorepo). В этой статье мы разберём, что такое монорепозиторий, зачем он нужен в Swift-разработке и как его организовать на практике.
Что такое монорепозиторий и зачем он нужен?
Монорепозиторий (monorepo) — это подход к управлению кодом, при котором все проекты, библиотеки и модули хранятся в одном репозитории (например, в Git). В отличие от мультирепозиториев, где каждый проект живёт в своём отдельном хранилище, монорепозиторий объединяет всё в одном месте.
Для Swift-разработчиков это даёт несколько преимуществ:
- Единая версия зависимостей — вы точно знаете, какая версия библиотеки используется во всех проектах.
- Упрощённый рефакторинг — изменение API в одной библиотеке сразу видно во всех зависимых проектах.
- Общие настройки — один SwiftLint, один CI/CD конфиг, одни правила форматирования.
- Лёгкий поиск — всё в одном месте, не нужно переключаться между репозиториями.
Однако есть и нюансы: монорепозиторий требует дисциплины и правильной структуры, иначе он превращается в «свалку». Но для начинающих это отличный способ научиться организовывать код.
Структура монорепозитория для Swift-проектов
Предположим, вы создаёте приложение для iOS с общими модулями. Типичная структура монорепозитория на Swift может выглядеть так:
MyMonorepo/
├── Apps/
│ ├── iOSApp/ # Основное iOS-приложение
│ │ ├── Sources/
│ │ ├── Resources/
│ │ └── Package.swift
│ └── macOSApp/ # macOS-версия
│ ├── Sources/
│ └── Package.swift
├── Packages/
│ ├── Core/ # Общие утилиты (сети, логирование)
│ │ ├── Sources/
│ │ └── Package.swift
│ ├── UIComponents/ # Переиспользуемые UI-компоненты
│ │ ├── Sources/
│ │ └── Package.swift
│ └── Analytics/ # Модуль аналитики
│ ├── Sources/
│ └── Package.swift
├── Scripts/ # Вспомогательные скрипты
├── .swiftlint.yml # Единый конфиг линтера
├── Package.swift # Корневой манифест (опционально)
└── README.md
Обратите внимание: здесь используется Swift Package Manager (SPM) — стандартный инструмент для управления зависимостями в Swift. Каждый модуль — это отдельный Swift Package, который можно подключать к приложениям.
Как создать монорепозиторий на Swift: пошаговое руководство
Давайте создадим простой монорепозиторий с двумя модулями и одним приложением. Выполните следующие шаги в терминале:
Шаг 1: Создайте корневую директорию и инициализируйте Git
mkdir MyMonorepo
cd MyMonorepo
git init
Шаг 2: Создайте структуру папок
mkdir -p Apps/iOSApp/Sources
mkdir -p Packages/Core/Sources
mkdir -p Packages/UIComponents/Sources
Шаг 3: Создайте Package.swift для модуля Core
Создайте файл Packages/Core/Package.swift:
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "Core",
platforms: [
.iOS(.v16),
.macOS(.v13)
],
products: [
.library(
name: "Core",
targets: ["Core"])
],
targets: [
.target(
name: "Core",
path: "Sources")
]
)
Шаг 4: Напишите простой код в модуле Core
Создайте Packages/Core/Sources/Logger.swift:
import Foundation
public struct Logger {
public static func log(_ message: String) {
print("[Core] \(message)")
}
public init() {}
}
Шаг 5: Создайте Package.swift для UIComponents
Аналогично создайте Packages/UIComponents/Package.swift:
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "UIComponents",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "UIComponents",
targets: ["UIComponents"])
],
dependencies: [
.package(path: "../Core")
],
targets: [
.target(
name: "UIComponents",
dependencies: ["Core"],
path: "Sources")
]
)
Шаг 6: Создайте простой UI-компонент
Создайте Packages/UIComponents/Sources/MyButton.swift:
import SwiftUI
import Core
public struct MyButton: View {
let title: String
let action: () -> Void
public init(title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
public var body: some View {
Button(action: {
Logger.log("Button '\(title)' tapped")
action()
}) {
Text(title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
Шаг 7: Создайте iOS-приложение, которое использует модули
Создайте Apps/iOSApp/Package.swift:
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "iOSApp",
platforms: [
.iOS(.v16)
],
dependencies: [
.package(path: "../../Packages/Core"),
.package(path: "../../Packages/UIComponents")
],
targets: [
.executableTarget(
name: "iOSApp",
dependencies: [
"Core",
"UIComponents"
],
path: "Sources")
]
)