
Привет, Хабр! Меня зовут Алексей Григорьев, я iOS-разработчик в МТС, работаю над продуктом Membrana — это тариф + мобильное приложение для управления приватностью в Сети. Сейчас в iOS-приложениях на первое место постепенно выходит модульная архитектура. Она позволяет создавать гибкие и масштабируемые проекты, которые легко сопровождать и развивать. Особенно это актуально при увеличении кодовой базы и разрастании функциональности, когда поддержка монолита усложняется.
Модульный подход не просто разделяет приложение на более управляемые части, но и закладывает возможность многократного использования кода в других проектах, позволяет организовать разработку и тестирование функциональных блоков независимо от основного приложения.
В этом материале я расскажу про ключевые моменты модульной архитектуры в iOS-приложениях, инструментах для ее внедрения и применения в наших проектах.
Зачем нужна модульность
Монолитная архитектура удобна на начальных этапах разработки: весь код в одном проекте, зависимости настраиваются быстро, легко вносить правки в ранние версии продукта. При таком подходе не надо думать про версионность и переиспользование, про других потребителей этого кода, межмодульные оптимизации и прочие нюансы.
Однако со временем проект разрастается, в нем появляются тесно связанные и взаимозависимые части, а любое изменение в коде может затронуть критичные элементы системы. Это ведет к увеличению числа ошибок, сложностям в отладке и значительно замедляет разработку. Поддержка монолита становится трудоемкой и требует больших ресурсов — даже средней по размерам команде будет некомфортно «толкаться» в одном проекте.
При модульной архитектуре приложение разбивается на независимые компоненты — модули. Это дает ряд преимуществ:
Каждый модуль имеет четкую зону ответственности, что снижает риск неожиданных ошибок и упрощает отладку.
Благодаря изоляции модулей их легко покрывать тестами и делать тестирование более управляемым.
Гарантированно отсутствует связанность с другими частями проекта.
Приложение проще расширять и модифицировать, так как каждое добавление функциональности — это отдельный пакет.
Модули можно выделить как функциональные блоки, которые будет разрабатывать и тестировать независимая команда, в отрыве от основного приложения. Да, интеграцию все равно нужно проверять, но сама разработка отдельных модулей идет проще.
Инструменты для создания модульной архитектуры
Реализовать модули в iOS-приложениях можно по-разному.
Подпроекты в Xcode
Один из самых простых способов разделить проект — создать несколько подпроектов внутри одного репозитория. Это позволяет разбить код на части и структурировать его.
Плюсы: весь код находится в одном рабочем пространстве, что облегчает доступ к нужным частям проекта и упрощает начальную настройку.
Минусы: нет явных деклараций зависимостей в подпроектах (только импорты), нет версионности у зависимостей и самих подпроектов. Из-за этого есть высокий риск ошибки: при отсутствии строгих границ можно случайно подключить код из другого модуля. Такая структура со временем становится более запутанной и менее управляемой.
Бинарные фреймворки
Они позволяют выделить части кода в независимые сборки. Особенно полезен XCFramework, с помощью которого создаются кроссплатформенные библиотеки для разных архитектур.
Плюсы: уже можно разделить разработку по командам.
Минусы: нет встроенной системы управления версиями, что усложняет поддержку.
CocoaPods
CocoaPods — популярный инструмент для работы с зависимостями в iOS. Он позволяет легко подключать сторонние библиотеки и управлять их версиями.
Плюсы: проверенное и надежное решение, поддерживающее большое количество существующих библиотек.
Минусы: долгая установка из-за использования Ruby, развитие проекта прекратилось, идет только его поддержка.
Swift Package Manager (SPM)
Swift Package Manager — официальное решение от Apple, которое постоянно обновляется и уже интегрировано в Xcode.
Плюсы: гибкость в декларации зависимостей, работа с локальными и удаленными бинарными фреймворками (включая XCFramework) и возможность упаковки бинарей. При этом бинарный файл может находиться как локально в пакете, так и удаленно в хранилище артефактов. По сути Framework и XCFramework — это некие структуры для бинарных фреймворков, а SPM позволяет их подключить и дает возможность версионировать такие артефакты.
Минусы: при работе с SPM нужен опыт настройки процессов интеграции, так как это довольно капризный инструмент. В нем все еще много багов, с ним тяжело начать работать, требуются танцы с бубном при настройке CI/CD.
Как мы реализуем модульную архитектуру
В нашей команде мы используем два последних подхода:
SPM для всех собственных проектов. Это технология Apple, она развивается и закрывает наши потребности в простоте модификации кода, версионности и возможности подключать из git без централизованных подспеков.
CocoaPods для обеспечения совместимости с проектами, которые не работают с SPM.
И SPM, и CocoaPod — это два разных пакетных менеджера. Пакет представляет собой манифест, где описаны поддерживаемые платформы и минимально рабочие версии, какие ресурсы должны подключиться и какие продукты будут доступны извне. Возьмем, например, пакет SPM: помимо платформ и версий, он декларирует, какой продукт (имя и референс на таргет) будет доступен для подключения, а также может специфицировать тип линковки (авто, динамик, статик).
Нашей команде нужно менять различные зависимости из основного проекта, не переключаясь между ними. Это важно для внутренних модулей или общих утилитарных библиотек. Мы также можем заниматься разработкой отдельных модулей независимо от всего проекта. Для этого мы внедрили следующие подходы.
Локальное подключение библиотек
Так мы можем редактировать и тестировать библиотеку непосредственно из проекта. Чужие «жирные» зависимости мы тоже порой подключаем локально, чтобы не тратить время на обновление кэшей для SPM.
Представьте себе какую-нибудь стороннюю библиотеку вроде Sentry или Crashlytics на 800 Мб. При очистке кэшей каждый раз приходится сидеть и ждать, пока она скачается из интернета. Вместо этого мы ее сохраним себе, и при работе кода библиотека будет подтягиваться с компьютера, что сильно сэкономит время.
Интеграция через CI/CD
На этапе развертывания удаляются все локальные подключения, проверяется полноценная интеграция с удаленными библиотеками для тестирования версий.
Мы используем Xcodegen для автоматизации и шаблонизации работы с проектными файлами и планируем переход на Tuist — этот инструмент имеет хорошую документацию и становится общим трендом в iOS-разработке. Он написан на Swift, и проект c его помощью конфигурируется как сущность с очевидными свойствами, в отличие от Xcodegen, где это непонятное поле. Если ты в нем ошибся, то даже не узнаешь об этом — все соберется, но работать не будет.
Что нужно учесть при переходе к модульной архитектуре
Для ее успешной реализации:
решите, как разделить проект на модули, чтобы минимизировать зависимости между ними;
убедитесь, что каждый модуль изолирован и покрыт тестами;
четко фиксируйте границы модулей и их взаимодействие.
Если вы все сделали правильно, то модульная архитектура повысит производительность команды, упростит поддержку кода и разработка станет более предсказуемой.
На этом у меня все. Если у вас возникли вопросы, пишите их в комментарии — постараюсь ответить.