Монорепозиторий с Swift: организация кода для iOS и macOS проектов

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Монорепозиторий с 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 — тонкий слой, который собирает все модули вместе и инициализирует приложение.

Блоги

Рекомендаций по книгам