Стандартное представление Xcode-проекта сложно назвать комфортным для командной работы. Даже в небольших проектах часто возникают merge-конфликты после изменения состава исходников в разных ветках.  К тому же Xcode не предоставляет каких-либо решений для реализации потенциала модульных проектов, что снижает интерес к теме модуляризации среди iOS-разработчиков. Да, ограничения Xcode можно победить, но решением в основном является "винегрет" из сторонних инструментов, заправленный собственными Shell или Ruby скриптами, в которых мало кто разбирается. Но есть куда более изящное и комплексное решение — Tuist. С ним мы и познакомимся в этой статье. Tuist — это open source инструмент для работы со структурой проекта.  Его основная функция — генерация Xcode-проектов из специальных файлов с описанием проекта в удобном формате. В терминологии Tuist эти файлы называются манифестами. Как и другие подобные инструменты (XcodeGen, Struct и Xcake), Tuist синхронизирует структуру Xcode-проекта с иерархией файлов на диске. Это отлично решает проблему merge-конфликтов в файлах project.pbxproj, так как их можно вообще удалить из репозитория и добавить в .gitignore. От аналогов Tuist выгодно отличается способом описания проекта — всё на Swift, как и положено, с автодополнением и прочими благами IDE. Ещё одно важное преимущество Tuist — кастомные хелперы, которые позволяют расширять его возможности и переиспользовать код в манифестах. Арсенал Tuist не ограничивается одной лишь генерацией Xcode-проекта, но весь его потенциал раскрывается только в модульных проектах. Поэтому знакомство с ним построим по принципу от простого к сложному. Для установки Tuist достаточно выполнить команду в терминале: Tuist распространяется вместе с собственным менеджером версий, который позволяет использовать разные версии инструмента в разных проектах и гарантирует воспроизводимую среду. Для демонстрации возможностей Tuist воспользуемся специально подготовленным стартовым проектом, для этого склонируем его из репозитория: Это простой проект iOS-приложения пока без внешних зависимостей, состоит из 1 таргета, имеет 3 конфигурации сборки: Debug, Release и Beta.  План прост: сначала интегрируем Tuist в этот проект, затем попробуем добавить в него внешние зависимости, а в конце посмотрим на возможности для модуляризации. Первым делом рекомендуется зафиксировать версию Tuist для проекта, выполним команду в корневой папке: После выполнения указанная версия будет записана в файл .tuist-version, и все команды Tuist в этой папке будут выполняться с этой версией инструмента. Следующим шагом необходимо добавить манифест Tuist для нашего проекта, создадим файл Project.swift в корневой папке: Можно сразу открыть этот файл в Xcode, но тогда придётся описывать проект без автодополнения. К счастью, Tuist умеет создавать временную среду для удобного редактирования манифестов, и достаточно выполнить команду: В результате откроется временный Xcode-проект, в котором можно работать с манифестами, как с обычным проектом на Swift:  Созданный файл Project.swift находится в группе Manifest, добавим в него описание проекта: Наш таргет является iOS-приложением — указываем соответствующие значения в параметрах platform и product. С типом продукта сборки никаких проблем нет, поддерживаются и различные типы фреймворков, и виджеты, и App Clips. В качестве исходников таргета указываем содержимое папки App в параметре sources. Tuist умный и сам фильтрует файлы ресурсов, которые определяются отдельно в параметре resources. У нашего проекта пока нет зависимостей, поэтому в параметре dependencies передаём пустой массив. В параметре settings описаны все конфигурации таргета, каждый наследует одну из встроенных конфигураций: либо debug, либо release. Сами настройки сборки можно переопределить, указав путь к файлу xcconfig, либо передав их параметре settings. Манифест проекта обязательно должен содержать переменную типа Project с описанием проекта, в минимальном виде достаточно указать его название и таргеты в соответствующих параметрах name и targets. Теперь для генерации Xcode-проект всё готово, выполним команду в корневой папке: Эта команда создаст новый Xcode-проект вместе с воркспейсом, который откроется в Xcode, благодаря флагу --open.  Все конфигурации, исходники и ресурсы на месте, но появилась новая папка Derived. В ней находятся сгенерированные файлы, в нашем случае это интерфейс доступа к ресурсам. Увы, стандартные средства Xcode не предоставляют удобных и безопасных механизмов для доступа к ресурсам. В качестве примера рассмотрим получение цвета из ассетов: Такая запись может быть неудобной из-за использования строк: у них нет автодополнения, а переименование в ассетах требует ручного поиска всех объявлений этого ресурса в коде. Tuist решает эту проблему, используя встроенный SwiftGen, который генерирует удобный интерфейс доступа к ресурсам: Таким образом, благодаря автодополнению, работать с ресурсами становится намного проще. А если их состав изменится, достаточно перегенерировать проект командой tuist generate, и компилятор покажет, что нужно поменять в коде. С основой разобрались, пора научиться подключать внешние зависимости. Tuist поддерживает все используемые в iOS-разработке менеджеры: Swift Package Manager, Carthage и CocoaPods. Для примера будем подключать SnapKit, используя Swift Package Manager. Откроем манифест проекта Project.swift и добавим Swift пакет в параметре packages инициализатора Project: Далее, необходимо установить зависимость от SnapKit для самого таргета, передадим его в параметре dependencies инициализатора Target: Теперь достаточно выполнить команду tuist generate, остальное Tuist сделает сам. После завершения генерации в папке проекта появится файл .package.resolved с фиксированными версиями пакетов. Tuist предоставляет внушительную функциональность для реализации модульных проектов, попробуем самые вкусные его фичи на нашем примере. Из соображений простоты мы не будем использовать какие-либо архитектурные подходы для модуляризации, но важно понимать, что бессистемный "распил монолита" даёт больше вреда, чем пользы. Для реального проекта потребуется продуманная структура модулей и схема их связей. Как вариант, можно воспользоваться микрофичевой архитектурой, которую предлагает Tuist. В демо-проекте уже предусмотрена отдельная папка с исходниками для нового модуля чата, она находится внутри папки Features.  Наша задача — добавить файл манифеста для этого модуля и описать в нём проект. Но это мы уже умеем, а вот переиспользовать код в манифестах ещё не пробовали, поэтому сначала разберёмся с хелперами. Модульность подразумевает систематизацию структуры модулей. Проще говоря, их проекты будут похожи друг на друга, а следовательно, в манифестах продублируется много информации.  Эту проблему можно легко решить с помощью переиспользуемых хелперов, которые должны располагаться в папке Tuist/ProjectDescriptionHelpers, добавим её для нашего демо-проекта: Следующим шагом создаем файл Project+Feature.swift в этой папке: Остаётся выполнить команду tuist edit, найти этот файл в среде редактирования и добавить следующее расширение типа Project: Метод feature создаёт проекты модулей, которые отличаются только именем, зависимостями и файлами на диске. Мы уже знакомы с большинством параметров таргета и проекта, отличается только тип продукта сборки — staticFramework. Это значит, что наш модуль будет статически линкуемым фреймворком, что полезно, так как динамическая линковка деградирует время холодного старта приложения. Статически линкуемые фреймворки имеют одно неприятное ограничение — они не могут содержать ресурсы. Tuist решает эту проблему генерацией отдельного бандла для них, который автоматически подключается ко всем необходимым таргетам. Это отличная фича для модульных проектов, потому что полностью решает проблему холодного старта. Также стоит обратить внимание на параметр infoPlist, ему передается значение по умолчанию. В этом случае Tuist самостоятельно генерирует файл Info.plist в папке Derived и связывает его с проектом. Теперь, когда наш хелпер готов, его можно использовать для описания нового модуля чата, создадим файл манифеста в папке Features/Chat: Затем откроем среду редактирования командой tuist edit и добавим следующее содержимое в этот манифест: На этом манифест нашего модуля завершён, но этого ещё недостаточно, так как само приложение о нём ничего не знает. Чтобы Tuist генерировал и связывал проект для нового модуля, необходимо добавить зависимость на него в манифесте приложения: После выполнения команды генерации наш новый модуль появится в воркспейсе в виде отдельного проекта, а в зависимости таргета приложения добавится Chat.framework.  По умолчанию Tuist генерирует схемы для всех таргетов, и наш новый модуль не исключение. Это удобно, так как отдельные схемы позволяют не собирать весь проект, чтобы проверить ошибки в одном таргете. Но есть ещё одна фича Tuist, упрощающая разработку в условиях модульного проекта. Большие проекты содержат много таргетов и схем, что сильно увеличивает время индексации в Xcode. К тому же разработчик будет зря тратить время на сборку и терять фокус, когда ему нужно работать только с определённым участком кодовой базы. Для решения этой проблемы Tuist предлагает генерировать только необходимую часть проекта, убирая всё лишнее. Например, чтобы сфокусироваться на нашем таргете Chat, достаточно выполнить команду: После её выполнения сразу откроется Xcode, и в нём будет только проект Chat с единственным одноимённым таргетом. Команда фокуса убирает всё, от чего не зависят сами фокусируемые таргеты. Да, команде можно передать несколько таргетов для фокуса: Но и это ещё не всё. Tuist также позволяет предсобрать все модули и использовать готовые артефакты сборки для фокуса. Попробуем на нашем примере, подготовим кэш и сфокусируемся на таргете App: Полученный проект в Xcode уже не будет содержать зависимости сфокусированного таргета в виде исходников, только готовые артефакты, которые не пересобираются. Если зависимости изменились, можно повторно "подогреть" кэш командой tuist cache warm, и Tuist сам найдёт изменения и пересоберёт только необходимые таргеты.  Продемонстрировать все возможности Tuist в одной статье невозможно, и без внимания осталось ещё много вкусного: Это не значит, что Tuist делает всё сам, интеграция потребует больших ресурсов. Но если цель — сделать структуру проекта удобной и масштабируемой, то потраченные ресурсы быстро себя оправдают. В нашем проекте Tuist уже заменил кучу велосипедов на Ruby, которые поддерживались только узким кругом разработчиков. Инфраструктура стала дешёвой за счет Swift, а работа с проектом — настоящим удовольствием. С поддержкой нет никаких проблем, обновления выходят с завидной частотой. Tuist — это тот случай, когда issues смотришь не только для поиска проблем, но и для планирования улучшений. Например, мы очень ждём новый универсальный интерфейс для подключения сторонних зависимостей, который позволит абстрагировать проект от менеджеров Carthage, CocoaPods и Swift Package Manager. Следует привыкнуть, что Xcode-проект постоянно генерируется, значит, изменения настроек проекта следует производить в манифестах Tuist. А что касается файловой структуры проекта, тут всё по-старому: добавление, удаление и перемещение файлов происходит как обычно, так как их индексация не имеет значения. Буду рад обратной связи в комментариях. Пока! iOS разработчик