Добавить объявление

Модуляризация iOS-приложений через SPM: как навести порядок в зависимостях

Всем привет! Сегодня хочу обсудить статью, в которой автор делится подходом к организации зависимостей в крупных проектах с помощью локальных SPM-пакетов. Когда проект вырастает из пары десятков файлов, монолитный таргет начинает болеть. Сборки замедляются, изменение в одном месте перекомпилирует половину проекта, любой файл может импортировать что угодно.

Три слоя, один поток зависимостей:


Автор делит все приложение на три уровня:

  • Common - самые низовые утилиты: логгеры, расширения, хелперы. Не зависят ни от чего внутри проекта.

  • Services - два подмодуля: API (сетевые модели и эндпоинты) и Domain (бизнес-логика, сервисы, моки). Domain зависит от API и Common.

  • Features - экраны на SwiftUI. Импортируют только Domain и Common. Никогда - API.

Зависимости текут строго снизу вверх. Модуль может зависеть только от того, что лежит под ним.

Как это выглядит в Package.swift:


В манифесте все зависимости объявляются явно, с помощью удобного dot-синтаксиса. Каждый таргет знает, от кого он зависит. Если кто-то попытается импортировать модуль сетевого слоя прямо из фичи - проект просто не соберется. Компилятор не даст нарушить архитектуру.

Что в каждом модуле:


  • API - только модели, которые зеркалируют ответ сервера, и типизированные эндпоинты.

  • Domain - здесь происходит основная работа: модели предметной области, маппинг из API-моделей в доменные, сервисы (closure-based, без протоколов) и моки. Моки тоже живут здесь, потому что они возвращают доменные модели, а не API-шные.

  • Features - чисто UI. Импортируют только Domain, используют моки для превью. Никакого сетевого слоя внутри.

ServiceEnvironment для массовой инъекции:


Когда сервисов становится больше двух, можно использовать контейнер ServiceEnvironment, который собирает все сервисы в одну структуру. Через кастомный модификатор все прокидывается в окружение одной строкой. Превью используют .mock, приложение - .live.

Что дает такой подход:


  • Сборки ускоряются: поменяли фичу - перекомпилируется только фича.

  • Границы соблюдаются на уровне компилятора, а не код-ревью.

  • Тесты изолированы: домен тестируется без SwiftUI, фичи превьюшатся с моками.

  • Параллельная разработка: один пишет сетевой слой, другой - интерфейс на моках, конфликтов почти нет.

Вывод:


Модуляризация через локальные SPM-пакеты - это не про идеальную архитектуру ради архитектуры. Это про скорость и масштабируемость. Начинать лучше с малого: вынести сначала Domain, потом API, а фичи - по мере роста. Результат - приложение, где новый разработчик за пять минут понимает структуру, а CI не ждет десять минут на сборку.
23.05.2026 70 489