Основной инструмент любого программиста — язык программирования. Когда начинался проект мы выбрали Swift. Решили идти в ногу со временем, старый, но так горячо любимый Objective-C остался не у дел. Однако у Swift есть небольшая проблема и особенно она становится заметной, когда проект начинает расти – это проблема времени сборки проекта. Для понимания проблемы и размеров проекта, попробуем сравнить среднее время сборки за неделю на всех проектах студии.


image


Как видно из графика, медиана времени сборки проекта ZenitOnline больше всех представленных в несколько раз. И поверьте, остальные проекты не такие уж и маленькие. Особенный интерес представляет Objective-C проект, который в свою очередь по размерам сопоставим с нашим проектом. Оба содержат 100+ экранов. За полтора года разработки мы успели достичь следующих цифр:


  • ? 2500 файлов;
  • ? 600 ресурсов;
  • ? 160 000 строк кода.

Однако счастье длилось недолго и, примерно, на этапе 1500 файлов xcode решил, что нам больше не нужно собирать проект и просто отказался это делать, аргументируя свое поведение следующей ошибкой:


Unable to spawn process (Argument list too long)

В поисках ответа на SO, Swift Jira и Open Radar, я понял, что решить это в одно действие не получится. Исходя из появившейся ошибки, найти выход из этой ситуации можно несколькими способами:


  1. Уменьшить количество папок в проекте, тем самым скинуть все в одну кучу, чтобы путь до компилируемых файлов уменьшился. Это решение не зашло из-за любви к порядку в проекте.
  2. Переместить проект выше в иерархии папок в системе. Это спасло нас на какое-то время, но далее уменьшать путь до директории проекта стало невозможно.
  3. Последним решением, которое реально могло помочь нам на тот момент, стала модульная архитектура проекта. Собственно, этого способа решать проблемы мы придерживаемся и до сих пор.

Более подробный ресерч проблемы со слишком длинным путем до компилируемых файлов можно найти в моем ответе на SO.
Спустя десятки потраченных часов, нервов и баночек энергетика удалось разбить проект для начала на два модуля, которые покрывали общие переиспользуемые классы и сервисный слой, а потом уже на полноценный концепт модульной архитектуры приложения.


Безусловно, переход на модульную архитектуру дался очень нелегко. На каждом этапе появлялось все больше и больше подводных камней. Связный код не давал с легкостью разбивать проект на слои по ответственностям, но в итоге проект, который изначально полностью разрабатывался в одной области видимости, превратился в проект, разбитый на отдельные модули. У нас получилась схема ответственностей модулей:


image


3rd Party Dependency – самый нижний слой, отвечает за сторонние зависимости. Все эти сторонние библиотеки подключаются к проекту с помощью CocoaPods. Ничего необычного по сравнению с плоской архитектурой приложения.


Core Frameworks – после слоя со сторонними библиотеками идет основной блок фреймворков, которые в дальнейшем импортируются во все части приложения и используются так или иначе.


  • Common – модуль, в котором содержатся основные константные значения. Например, локализация, все картинки, extensions и другие не завязанные на UI и сервисном слое классы и утилиты.
  • Services – этот модуль содержит в себе все необходимые классы для работы с сетью и данными. Здесь можно найти модели сервисного слоя, утилиты для работы с сетью, БД и другими данными в приложении.
  • Reusable – в этот фреймворк мы решили вынести все переиспользуемые UI-элементы нашего приложения: поля ввода, ячейки, адаптеры и другие вьюшки.
  • Analytics – исходя из названия, думаю, легко понять, что все классы аналитики находятся тут.
  • Core – фреймворк, в котором не находится ни строчки кода. Единственная его цель – это объединение импортов всех модулей, входящих в Core слой. Это необходимо для дальнейшей удобной линковки переиспользуемых фреймворков в других модулях.

Feature Frameworks – это фреймворки, которые содержат в себе отдельно взятые фичи. Каждый фреймворк из этого слоя импортирует в себя Core уровень и другие зависимые от него фичи. Такой подход позволяет удобно изолировать работу и код разных разработчиков.


Выводы


Какую проблему решает модульность? Как минимум две проблемы были решены благодаря такому подходу:


  • Скорость сборки проекта.
  • Невозможность скомпилировать проект.

Скорость сборки проекта «на холодную» вообще не изменилась, так как количество необходимых для компиляции файлов не уменьшилось. Они просто стали собираться в разных стадиях, что позволило решить проблему бесконечно длинного пути до компилируемых файлов в основном модуле проекта. Однако уменьшилось время сборки проекта «на горячую», когда мы вносим изменения только в один модуль, который не используется в бесконечном количестве других частей проекта, то xcode собирает только то, что вы изменили. Конечно это не работает в случае, когда вы изменяете переиспользуемый элемент, который задействован в большом количестве мест в приложении.


Также, немаловажно — мы наконец-то смогли собрать проект. Проект начал билдиться без танцев с бубном.


P.S. Xcode 11 решает проблему с ошибкой компиляции слишком большого количества файлов. Конечно мы не отказались от модульной архитектуры и продолжаем развивать этот архитектурный подход в студии.


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