Site icon AppTractor

Почему большие проекты Swift утыкаются в стену (и как пробиться сквозь нее)

На дворе 2025 год, и разработка приложений Swift большого масштаба по-прежнему остаётся непростой задачей. Если вы только начинаете или ваш проект только растёт по объёму кода, количеству модулей или числу разработчиков, участвующих в нём, вы можете не чувствовать необходимости вносить какие-либо изменения в свою конфигурацию. Но если ваша кодовая база имеет приличный размер, вы, вероятно, время от времени сталкиваетесь с проблемами, которые снижают вашу производительность и мотивировку добавлять новые функции в приложение. В Tuist мы видим это каждый день.

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

В этой статье я хотел бы обсудить распространённые проблемы, с которыми мы продолжаем сталкиваться, и то, как Tuist помогает вам их преодолеть. Я также отмечу некоторые из проделанной и продолжающейся работы Apple по улучшению ситуации и, надеюсь, расскажу вам о многих проблемах, с которыми вы, вероятно, сталкиваетесь, но не можете определить первопричину. Но прежде чем мы углубимся в детали, давайте сначала поговорим о том, что я называю кризисом лидерства.

Кризис лидерства: просыпаемся

Приложения обычно являются всего лишь ещё одним интерфейсом для взаимодействия пользователей или клиентов компании с бизнес-средой, а конечная цель компании — увеличение выручки и прибыли. Выход на конкурентные рынки требует от них более быстрого развития, что часто приводит к напряжению в командах iOS-разработчиков, где, независимо от того, сколько человеческих ресурсов вы выделяете на решение проблемы, узким местом становится инструментарий и всё, что его окружает. Время сборки или время непрерывной интеграции (CI) пятилетней давности, которое раньше составляло 2 минуты, превратилось в 30 минут, и нет Apple Silicon, способного сократить эти 30 минут до 2. И неважно, насколько сильно вы обвиняете Apple — бизнесу всё равно, насколько вы нормализовали это время. Они считают это неприемлемым, особенно учитывая наличие других платформ, таких как веб-платформы, с гораздо более быстрыми и продвинутыми инструментами, которые обеспечивают им не только необходимую скорость, но и наглядное представление происходящего для оптимизации настройки.

Достигнув этой точки, мы часто видим, как компании выбирают несколько путей. Один из самых распространённых — внедрение React Native. Они добавляют в уравнение  среду выполнения JS, которая позволяет им выполнять горячую перезагрузку изменений в процессе разработки и даже распространять обновления и исправления «по воздуху». Shopify перенесла все свои мобильные приложения на React Native именно по этой причине. Они инвестировали в React до этого решения, и это было для них разумно, поскольку это также соответствовало бы мобильной модели мышления, с которой они уже знакомы в вебе. Но вам нужно помнить о компромиссе, на который вы идёте. Выбирая React Native, вы абстрагируетесь от нативной платформы, и это обойдётся вам недешево, как это было в случае с Shopify, где пришлось собрать команду для разработки и поддержки базовых компонентов или положиться на такие компании, как Expo, которые объединяют экосистему библиотек и сервисов под одной крышей.

Второй путь, который, как мы видим, выбирают некоторые компании, — это Bazel. Bazel — очень продвинутая и мощная система сборки. Но замена всей системы сборки часто обходится даже дороже, чем внедрение React Native. Большинство языков программирования и экосистем поставляются со встроенными системами сборки: Elixir, Swift, Xcode, Gradle, Cargo… Поэтому при замене на что-то другое согласование системы сборки со всем, что на ней построено — это огромная инвестиция, на которую готовы пойти немногие компании. В Tuist мы часто видим компании, история которых выглядит примерно так: один или несколько инженеров в восторге от Bazel, они убеждают руководство потратить несколько месяцев на внедрение Bazel, им удаётся заставить его работать, хотя никто, кроме них, не знает, как он работает, затем эти инженеры уходят из компании, и компания решает отказаться от Bazel из-за его высокой стоимости. Мы видели бесчисленное множество компаний, прошедших этот путь, и я их полностью понимаю. Старайтесь как можно больше времени уделять своей системе сборки, поскольку это также означает близость к окружающей экосистеме.

Тем, кто предпочитает оставаться ближе к нативному набору инструментов и, следовательно, иметь меньшие затраты на масштабирование разработки, Tuist представляется оптимальным вариантом. Так задумано. Мы абстрагировались именно там, где это было необходимо, чтобы помочь командам преодолеть трудности, одновременно отслеживая и устраняя абстракции, когда проблемы решались на нужном уровне, как это было с Xcode 16 и появлением папок для сборки.

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

1. Модуляризация

Модуляризация — это не просто инструмент для определения границ между компонентами. Это также инструмент для доступа к повторно используемым фрагментам кода в репозиториях и для совместного использования кода приложением и расширениями, содержащимися в нём. Рано или поздно вам придётся этим заняться. К сожалению, модуляризация имеет свою цену, в том числе связанную с решениями Xcode.

Как вы, возможно, знаете, двоичные файлы могут быть скомпонованы статически или динамически. Статическая линковка происходит во время сборки, а динамическая — во время выполнения. Выбор того или иного варианта для каждого модуля в графе имеет последствия, выходящие за рамки простого выбора типа линковки. Например, динамические модули необходимо явно копировать в нужный продукт, чтобы компоновщик мог их найти. Или статические библиотеки придётся скомпоновать из определённых продуктов, которые могут компоновать статические библиотеки, чтобы избежать дублирования символов. Реализовать всё это правильно легко, если в графе несколько модулей, но по мере роста сложности ни один человеческий мозг не может хранить граф в памяти, чтобы настраивать его и устранять возникающие проблемы.

Как и в случае с derived data, люди испытывают эту боль. И проблема была настолько сильной, что они решили, что SwiftPM — лучшее решение для её решения. «Эти люди разобрались с определением модулей, значит, они, должно быть, разобрались, как согласовать граф SwiftPM с графом проекта Xcode», — должно быть, подумало сообщество. Таким образом, без всякого планирования, Apple пришлось согласовывать граф SwiftPM с графом проекта Xcode, который также мог содержать неявно определённые зависимости. Должно быть, это было увлекательной задачей для Apple. И словно этого было недостаточно, они добавили дополнительную переменную, чтобы сделать задачу ещё сложнее: объединяемые библиотеки.

В последние годы мы видели, как всё больше компаний передают на аутсорсинг сложные графы SwiftPM, а вскоре осознали, что им пришлось пожертвовать эргономикой разработки ради снижения сложности поддержки графа. Теперь проекты выполняются асинхронно, порой это невозможно контролировать, и проект, который раньше был готов к компиляции при запуске, больше не готов, потому что теперь он вызывает в фоновом режиме процесс SwiftPM, работу которого необходимо согласовать с устаревшей бизнес-логикой и пользовательским интерфейсом Xcode. Весело, весело.

Это сложная тема для обсуждения, поскольку одна из причин этого — сам Swift и манифесты Package.swift, которые необходимо компилировать. Некоторые из них могут содержать неявные побочные эффекты, о существовании которых они не знают, но предполагают, как это автоматически проявляется при загрузке подмодулей. Удивительно писать манифесты на Swift — Tuist тоже этим занимается — но когда это приводит к ухудшению опыта разработки в определённых масштабах, я сомневаюсь, что это хорошая идея. Для сравнения, Cargo, система сборки Rust, использует сериализуемые файлы .toml. Экосистема JavaScript использует .json, а Bazel — язык Starlark, который является предметно-ориентированным и оптимизирован для систем сборки.

Для Tuist главной целью было сделать модуляризацию максимально простой, наряду с предотвращением частых конфликтов с git, с самого начала. Это отличало нас от других инструментов генерации и стало основой, необходимой для оптимизации проектов команд. Возможно, я предвзят, но Tuist — лучший инструмент модуляризации. SwiftPM был разработан для разрешения зависимостей, неплохо справляется с этой задачей и должен оставаться инструментом для этого. Проекты Xcode должны развиваться, чтобы упростить модуляризацию, чтобы SwiftPM не использовался в качестве менеджера проектов. Это признак того, что Xcode и проекты Xcode требуют итерации дизайна. До тех пор, я думаю, генерация проектов, как это делает Tuist, — лучший путь вперёд. Всё необходимое для правильной компоновки, от этапов сборки до её настроек, уже сделано за вас, так что вы можете сосредоточиться на структуре графа, а не на деталях реализации, которые делают это возможным в проекте Xcode.

2. Время сборки

Каждый год выходит новый Apple Silicon, и ваши менеджеры спешат убедить руководство купить его, потому что это может помочь сократить время сборки ваших проектов. Но со временем сокращения становились всё меньше и меньше. Обосновать инвестиции становилось всё сложнее, и вам нужна альтернатива. К сожалению, React Native и Bazel не подходят, и, к сожалению, Xcode вам в этом не поможет.

Это основной стимул для организаций внедрять Tuist. Они хотят сократить время сборки. В зависимости от того, кого вы спросите, что такое «медленно», вы получите разные ответы, но мы видели, как они варьируются от «Мне надоели мои 5-минутные сборки» до «Мне надоели мои 2-часовые сборки в непрерывной интеграции». Эти сроки стали ужасающе нормальными в экосистеме. И всё становится ещё хуже, когда люди очищают производные данные, чтобы смягчить непонятную им проблему Xcode. «Разве они не могут выполнять несколько задач одновременно?» — можете спросить вы. Как будто многозадачность — это ответ на все вопросы. Многозадачность означает переключение контекста, а переключение контекста ещё и ужасно затратно.

Вскоре после того, как мы упростили модуляризацию с помощью сгенерированных проектов, мы обнаружили, что у нас есть подходящая основа для оптимизации времени сборки, изучив опыт таких проектов, как Carthage или Bazel, и мутировав граф, чтобы включить в него двоичные файлы и гарантировать, что наша логика генерации согласует все изменения. Результат? Система кэширования, которая помогает командам добиться улучшения времени сборки до 80% (например, Trendyol достигла 65%). Эффективность сильно зависит от модуляризации и распределения вкладов между всеми модулями. Как минимум, вы кэшируете все сторонние зависимости. Ведь кому захочется компилировать код, который вы практически не меняете в чистых сборках?

Вы можете посмотреть на общедоступный дашборд Tuist, чтобы увидеть, как наша текущая эффективность кэширования колеблется около 84%. Или, например, возьмём этот тестовый прогон, где 122 модуля были заменены двоичными файлами. Мы надеемся, что кэширование в конечном итоге станет нативной функцией в системе сборки, и CAS — правильный шаг в этом направлении. В Tuist мы готовимся к этому, создавая самое быстрое решение с минимальной задержкой, чтобы пользователи могли без проблем подключать свои проекты Xcode в ближайшем будущем.

3. Время тестирования

Сборка — это лишь часть работы команд после внесения изменений. Им также необходимо запускать тесты. Локально разработчики обычно запускают тесты, которые были изменены, или те, которые связаны с изменениями файлов. Однако в непрерывной интеграции команды обычно запускают все тесты тестового набора. Это означает, что чем больше тестов они добавляют, тем медленнее они будут выполняться в непрерывной интеграции, и в какой-то момент вам придётся что-то с этим делать.

Мы затронули некоторые стратегии, но общая идея заключается в том, что сначала следует рассмотреть возможность распараллеливания внутри среды, поскольку среды непрерывной интеграции предоставляют доступ ко всем ядрам Apple Silicon. Это влечет за собой необходимость убедиться, что ваш код разработан для распараллеливания, что часто не так, проявляясь в состояниях гонки, которые выглядят как нестабильность или, что ещё хуже, гонка данных. Если этого недостаточно, можно распараллелить код между средами, но для этого потребуется выполнить своего рода шардинг тестов между средами и поэкспериментировать с флагами -build-for-testing и -test-without-building. Мы планируем решить проблему шардинга в Tuist, чтобы разработчики могли автоматически делать это, используя флаги в CLI, вместо того, чтобы писать собственные скрипты. Если этого недостаточно, последнее, что вы можете сделать, — это выбор тестов, которые должны запускаться в зависимости от изменений в файлах.

В Tuist мы предлагаем выборочное тестирование, которое работает как со сгенерированными проектами, так и с чистыми проектами Xcode, и использует нашу логику отпечатков, которая не зависит от Git и проверена многими крупными организациями со сложными проектами Xcode. Внедрение довольно простое:

tuist xcodebuild test -workspace App.xcworkspace -scheme App
# Instead of
xcodebuild test -workspace App.xcworkspace -scheme App

4. Аналитика

Вы создаёте или запускаете тесты в своём проекте Xcode, пользовательский интерфейс Xcode отображает результат, и эти данные остаются там, в вашей среде, как будто ничего не произошло, пока вы случайно не удалите производные данные. В непрерывной интеграции всё выглядит так же. Ваши сборки запускаются, и вывод их состоит из двух вещей: пройдена ли она или нет, и журналов xcodebuild, часто форматируемых с помощью инструментов форматирования, таких как xcbeautify. Изолированность и эфемерный характер этой информации мешают командам получить полную картину, необходимую для ответа на следующие вопросы: мои сборки становятся быстрее или медленнее? А как насчёт моих тестов? Растёт ли размер моего приложения? Появились ли новые нестабильные тесты? В других экосистемах, таких как веб, сбор, обработка, хранение и анализ данных — распространённая практика. В Apple — нет, несмотря на важность этой практики для принятия обоснованных решений по улучшению процесса разработки.

Что касается причин, по которым это встречается не так часто, у меня есть несколько предположений. Первая причина заключается в том, что данные, экспортируемые инструментами Apple, являются строго проприетарными, что добавляет дополнительный уровень косвенности, требующий определённого конвейера обработки перед экспортом данных в системы для их анализа. Файлы .xcresult и .xcactivitylog — хорошие примеры. Эти данные никогда не предназначались для использования вне Xcode, поэтому для них отсутствует документация и инструменты для обработки. Вторая причина, на мой взгляд, заключается в том, что для хранения данных требуется база данных, которая в некоторых случаях может представлять собой нечто большее, чем просто стандартная база данных SQL, например, ClickHouse, и сервер, способный аутентифицировать и авторизовать доступ к базе данных. Это системы, с которыми, во-первых, команды iOS-разработчиков обычно не знакомы и не горят желанием заниматься их разработкой и поддержкой, а во-вторых, им сложно получить поддержку руководства. В итоге это становится хобби-проектом внутри компании, который часто принимает форму экспериментов со Swift в серверной среде. Впечатляет, сколько всего удалось создать другим командам, но этого недостаточно.

Чтобы данные были полезными, их необходимо представлять в удобном виде. Также необходимо сопоставлять данные для получения новой полезной информации. Кроме того, необходимо создать систему, позволяющую определять критические точки и помогать командам отслеживать, если что-то идёт не по плану. Это очень много. Именно это мы и создаём вместе с Tuist, чтобы руководство компаний могло передать эти задачи на аутсорсинг Tuist и сосредоточить свои инженерные ресурсы на разработке приложений. Вы можете ознакомиться с нашей общедоступной панелью Tuist, доступной только для чтения, чтобы получить представление о данных, которые вы сможете получить для своих команд. Например, с помощью одного scheme post action вы можете получать аналитику сборки на дашборде, и вскоре то же самое можно будет сделать с результатами тестирования.

Вы можете продолжать работать вслепую, и, возможно, достигнете многого, но без данных вам будет сложно общаться с руководством, особенно когда наступают моменты вроде: «Я слышал о React Native. Что вы думаете?».

5. Производные данные

Кто не знаком с производными данными (Derived Data)? Это папка, которую все удаляют при сбое компиляции. Та самая папка, удаление которой приводит к чистой сборке, что в некоторых случаях может быть довольно медленным. Производные данные оказались неудачным решением, последствия которого ощущаются в долгосрочной перспективе. Это решение привело к тому, что многие проекты в реальной жизни компилируются не потому, что явно импортируют зависимости, а потому, что их можно случайно обнаружить в производных данных. Неявный импорт может привести к ненадёжной работе функций редактора, таких как макрорасширения или предварительный просмотр Swift, из-за того, что редактор разрешает недопустимый граф, из-за чего в производных данных отсутствуют отсутствующие двоичные файлы, необходимые для работы этих функций редактора. Как бы безумно это ни звучало, всё дело в производных данных, но мы мало об этом говорим — слишком мало, на мой взгляд. Мы шутим о неработающих предварительных просмотрах или очистке производных данных время от времени, но проблема действительно серьёзная.

Хорошая новость заключается в том, что Apple знает об этом и работает над этим, используя другой подход, называемый хранилищем с адресацией по содержимому (CAS) и явными модулями, к которому они постепенно подталкивают пользователей. Таким образом, вместо использования одного общего каталога система сборки использует хэши для поиска артефактов компиляции из предыдущих компиляций и использует их вместо вызова компилятора. Это работает в случаях, когда у вашего проекта нет неявных зависимостей. Дело вот в чём: внедрение такой системы требует от Apple не только внедрения явных модулей, но и подталкивания экосистемы к их внедрению. На мой взгляд, это было слишком мягкое и не совсем понятное «зачем это нужно». Возможно, потому, что это требует признания прошлого решения по проектированию, которое привело к проблеме с производными данными. Я думаю, что это нормально. Я бы предпочёл, чтобы Apple открыто и прозрачно рассказала об этом и о том, как они подходят к решению этой проблемы, чем обсуждать это внутри компании, оставляя нас искать связи между некоторыми функциями Xcode и причинами их возможного внедрения.

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

Как я уже упоминал ранее, я уверен, что надёжность многих функций Xcode тесно связана с явностью графа проекта, поэтому чем меньше неявности, тем лучше. К сожалению, ни Apple, ни сообщество недостаточно пропагандируют эту идею, поэтому часто это воспринимается как послание в пустоту.

Заключение

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

Эти решения делают любой опыт агентного программирования ещё более приятным, поскольку в конечном итоге любые трудности, с которыми сталкиваются люди, будут ещё более раздражающими из-за агентов. От медленных циклов обратной связи при компиляции до отсутствия информации для сеансов программирования. Именно поэтому мы считаем критически важным в платформе обеспечить прозрачность и открытость данных, с которыми мы работаем, чтобы агенты имели к ним доступ в любое время.

Масштабирование разработки приложений на Swift стало для нас задачей, которой мы одержимы и которая продолжает нас вдохновлять. Если какая-либо из этих задач нашла отклик у вас и вы хотите поделиться ими с нами, напишите нам по адресу contact@tuist.dev, и давайте пообщаемся. Мы здесь, чтобы помочь любой организации, потому что мы хотим, чтобы организации более эффективно выполняли свои миссии.

Источник

Exit mobile version