Меня зовут Дима, я iOS инженер-менеджер в крупнейшем телеком-операторе Казахстана. У нас 19 разработчиков — и билд-тайм для нас важная составляющая разработки. В этой статье я пройдусь по следующему пути:
рассмотрю стратегии, которые вы сможете сразу применить;
покажу реальные цифры из наших проектов;
сделаю выводы и поделюсь инсайтами.

Проблема

Разработчики часто игнорируют билд-тайм, пока он не превращается в проблему. Возможно, поставить утром сборку на 30 минут и пойти пить кофе — это круто, но я считаю, это неэффективно. Если представить человека, который постоянно пытается ускорить билд-тайм, то вот он: руки у него в копоти, потому что он по 50 раз запускал билд-тайм, и руки его горят.
Три кита оптимизации
По известной пирамиде тестирования мы прекрасно понимаем, что больше всего у нас будет unit-тестов, интеграционных тестов, и меньше всего — UI-тестов, потому что они дороже. Эта статья будет построена на трёх китах с картинки.

Вертикальное скалирование
Начнём с вертикального скалирования — самый доступный и дешёвый способ ускорения билда — обновление оборудования. Например, переход с MacBook 2018 года на базовую модель MacBook Pro с процессором M1 позволил сократить билд-тайм с 348 до 136 секунд. Всю историю этих изменений можно найти в репозитории XcodeBenchmark.

Оптимизация проекта
Тут давайте сначала о другом: прежде чем начинать ускорение билд-тайма, вам нужны цифры. Цифры, которые можно будет показать команде как доказательство проблемы. Итак, профилирование.
Профилирование помогает замерять текущие показатели сборки и находить узкие места. В Xcode для этого есть инструмент Build with Timing Analysis, который показывает:
Время сборки каждого модуля.
Зависимости, которые замедляют процесс.

В одном нашем проекте долгое время занимала компиляция ассетов из-за одной некорректной картинки. После её замены проблема была решена.
Теперь непосредственно к оптимизации проекта. Этого кита я делю ещё на три: конфигурация, исходники и зависимости.
Конфигурация проекта
Хоть Apple и cтарается делать комфортно для всех, у проектов есть всё-таки своя специфика. Например, часто ребята не понимают разницу между дебаг и релизной сборкой: в дебаг-сборке можно отключить неиспользуемые архитектуры и оптимизации, которые вам не нужно тащить при каждом билде.

Также вы можете настраивать Optimization Level, когда вам не нужно каждый день улетать в App Store.

И таких настроек у Apple достаточно много:
Link Time Optimization
Precompile Prefix Header
Swift Compiler Optimization Level
Eager Link
Parallel Build Tasks
Clean Derived Data
Особенно Clean Derived Data — любимый пункт у айосеров. Помню, было модно отдельно запрограммировать кнопку на тачбаре под эту настройку — и чистить её через день, потому что она ломала всё, что есть. Но сделана она всё равно круто.
Настройки
Теперь стоит проверить две главные настройки для ускорения билда — Increment build и Parallel build. Инкрементная сборка позволяет повторным билдам обрабатывать только изменённые файлы. А для параллельной сборки Apple каждый год старается выжимать максимум из своего железа — и после такой многозадачности вы будете экономить всё больше времени.
И напоследок, не забудьте проверять Script phases. Бывает, приходит новенький, затаскивает новый скрипт, который ускоряет работу, а используется раз в месяц. А наш билд-тайм увеличивается на 20 секунд.
А если хотите ещё больше инфы, WWDC — это та самая крутая конференция, которая проходит каждый год. Помимо презентации новых айфонов и макбуков, Apple уделяет внимание ускорению билд-тайма. Вот несколько моих любимых материалов:
Исходный код
Второй кит — это то, что мы пишем каждый день. Немного цифр:
3 приложения (2 основных, 1 внутренний продукт),
200+ тысяч строк кода,
20+ внутренних модулей,
50+ внешних зависимостей.
Что делать с кодом? Не писать? За это нам не заплатят :-) Но зато есть «мёртвый» код — код, который не используется, но всё ещё существует в проекте. Мы активно его удаляем, чтобы повысить наш coverage. Зачем покрывать то, что не используется?
Есть такой инструмент Periferry — он показывает при билде, какой код реально используется, а какой нет. Благодаря нему мы удалили 25 000 строк, а это 10% кодовой базы.
Если разработчики не хотят удалять свою мертвую прелесть, отлично — предложите им положить прелесть в Swift Packages.
А ещё лучше — сделать из неё бинарный файл. Такой код будет скомпилирован, и что бы вы ни меняли, это не будет влиять на билд-тайм. Вы можете кэшировать файл, например, локально на своем макбуке или на сервере для коллег.

Зависимости
В своих проектах мы используем SPM (Swift Package Manager) — он позволил нам ускорить работу с зависимостями.
Но у SPM есть свои проблемы: у нас тоже бывают баги, крашимся, всё что угодно. Например, одна из библиотек после клонирования занимала 786 МБ. Посмотрели исходники в Github — 200 Кб, посмотрели Binary Library — 12 МБ.
786–12=774!!! — что-то математика не сходится.
По сути, SPM клонирует всю репу. Там есть исходный код, есть картинки, есть что-то другое… а есть история! Тут-то вся и проблема. Если вы закидываете бинарные файлы в гит, он их не заменяет, он их хранит — всю свою жизнь. Поэтому, когда вы будете стягивать SPM, посмотрите, откуда тянутся ваши бинарные файлы.
Оптимизация билд-системы
Третий кит — это попробовать изменить способ работы с билдом. Проект — это тяжелая вещь, где мы должны организовать тысячи файлов, сотни зависимостей. У себя мы используем Tuist. Конечно, есть свои плюсы и минусы, и мы понимаем, куда можно расти.
Цифры после всех оптимизаций и Tuist
Замеряли мы на Macbook Pro 14 M2, а проектом выбрали приложение Altel. Билд-тайм с SPM на холодную без стягивания зависимостей занимал 130 секунд, вроде не так страшно.
Если ускорить с Tuist без бинарного кэша — выходит 70–85 секунд. Всё потому, что Tuist умеет оптимизировать зависимости — делать из неявных явные.
Если ещё кэшировать внешние пакеты — выходит 33 секунды. Цифры, конечно, могут меняться от тех или иных условий.
Но я как-то подумал:, «Ччто, если закэшировать все наши библиотеки?» — получилось 20 секунд. Ускорение в 6 раз.!
А вот и пруфы. Слева можно увидеть, насколько изменилось время, которое тратилось на анализ зависимости — он занимает меньше 5 секунд. Справа — как билдится исходный код проекта. Конечно, мы тратим время на построение кэша, но не всё же в этом мире — бесплатно. :-)

Пару слов под конец
Мой личный посыл в этой статье — не бойтесь экспериментировать. Когда долго сидишь в комфорте, начинается стагнация. Будьте вечным студентом, который зайдет и сломает всем проект в Xcode, запушит это в гит. Кто-нибудь его скачает, спросит: «Кто сломал проект %:№()#???», и ты такой смотришь: «А это мой коммит :-)». И всё!
Редактор: Ефрат Гараев