Модульная архитектура (TMA)
TMA - это архитектурный подход к построению приложений для Apple OS, позволяющий обеспечить масштабируемость, оптимизировать циклы сборки и тестирования, а также обеспечить хорошую практику в вашей команде. Его основная идея заключается в том, чтобы строить приложения, создавая независимые функции, которые связаны между собой с помощью четких и понятных API.
В этом руководстве представлены принципы архитектуры, которые помогут вам определить и организовать функции приложения на разных уровнях. Здесь также представлены советы, инструменты и рекомендации, если вы решите использовать эту архитектуру.
µFEATURES
Ранее эта архитектура была известна как µFeatures. Мы переименовали ее в модульную архитектуру (TMA), чтобы лучше отразить ее назначение и принципы, лежащие в ее основе.
Основной принцип
Разработчики должны иметь возможность собирать, тестировать и пробовать свои функции быстро, независимо от основного приложения, обеспечивая при этом надежную работу таких функций Xcode, как предварительный просмотр пользовательского интерфейса, завершение кода и отладка.
Что такое модуль
Модуль представляет собой функцию приложения и является комбинацией следующих пяти целей (где цель означает цель Xcode):
- Источник: Содержит исходный код функции (Swift, Objective-C, C++, JavaScript...) и ее ресурсы (изображения, шрифты, раскадровки, xibs).
- Интерфейс: Это сопутствующая цель, содержащая общедоступный интерфейс и модели функции.
- Тесты: Содержит модульные и интеграционные тесты.
- Тестирование: Предоставляет тестовые данные, которые можно использовать в тестах и в примере приложения. Он также предоставляет имитаторы для классов модулей и протоколов, которые могут быть использованы другими функциями, как мы увидим позже.
- Пример: Содержит пример приложения, с помощью которого разработчики могут опробовать функцию в определенных условиях (разные языки, размеры экрана, настройки).
Мы рекомендуем придерживаться соглашения об именовании целей, которое вы можете применить в своем проекте благодаря DSL Tuist.
| Цель | Зависимости | Содержание |
|---|---|---|
Характеристика | FeatureInterface | Исходный код и ресурсы |
FeatureInterface | - | Публичный интерфейс и модели |
FeatureTests | Характеристика, FeatureTesting | Модульные и интеграционные тесты |
FeatureTesting | FeatureInterface | Тестирование данных и моделирование |
FeatureExample | FeatureTesting, Feature | Пример приложения |
::: Предварительные просмотры пользовательского интерфейса
Функция может использовать FeatureTesting в качестве актива разработки, чтобы обеспечить предварительный просмотр пользовательского интерфейса.
:::
КОМПИЛЯТОР ДИРЕКТИВОВ ВМЕСТО ТЕСТОВЫХ ТАРГЕТОВ
В качестве альтернативы можно использовать директивы компилятора для включения тестовых данных и моков в цели Feature или FeatureInterface при компиляции для Debug. Вы упростите граф, но в итоге скомпилируете код, который не понадобится для работы приложения.
Зачем нужен модуль
Понятные и лаконичные API
Когда весь исходный код приложения живет в одной цели, очень легко создать неявные зависимости в коде и в итоге получить так хорошо известный спагетти-код. Все сильно связано, состояние иногда непредсказуемо, и внесение новых изменений превращается в кошмар. Когда мы определяем функции в независимых целях, нам необходимо разработать публичные API как часть реализации функций. Нам нужно решить, что должно быть общедоступным, как наша функция должна потребляться, а что должно оставаться приватным. У нас больше контроля над тем, как мы хотим, чтобы клиенты нашей функции использовали ее, и мы можем следить за соблюдением правил, разрабатывая безопасные API.
Маленькие модули
[Разделяй и властвуй] (https://en.wikipedia.org/wiki/Divide_and_conquer). Работа с небольшими модулями позволяет больше сосредоточиться и тестировать и испытывать функцию изолированно. Кроме того, циклы разработки значительно ускоряются, так как компиляция происходит более избирательно, компилируются только те компоненты, которые необходимы для работы нашей функции. Компиляция всего приложения необходима только в самом конце нашей работы, когда нам нужно интегрировать функцию в приложение.
Возможность повторного использования
Повторное использование кода в приложениях и других продуктах, таких как расширения, поощряется с помощью фреймворков или библиотек. Создание модулей и их повторное использование - довольно простая задача. Мы можем создать расширение iMessage, расширение Today или приложение для watchOS, просто объединив существующие модули и добавив (при необходимости) специфические для платформы слои пользовательского интерфейса.
Зависимости
Когда модуль зависит от другого модуля, он объявляет зависимость от своего целевого интерфейса. Это дает двойную выгоду. Это предотвращает привязку реализации модуля к реализации другого модуля и ускоряет чистые сборки, поскольку им приходится компилировать только реализацию нашей функции, а также интерфейсы прямых и транзитивных зависимостей. Этот подход вдохновлен идеей SwiftRock Reducing iOS Build Times by using Interface Modules.
Зависимость от интерфейсов требует, чтобы приложения строили граф реализаций во время выполнения и внедряли его в модули, которым он нужен. Хотя TMA не имеет собственного мнения о том, как это делать, мы рекомендуем использовать решения с инжекцией зависимостей или паттерны, или решения, которые не добавляют индиректов во время выполнения и не используют API платформы, которые не были разработаны для этой цели.
Типы продуктов
При сборке модуля вы можете выбирать между библиотеками и фреймворками, и статической и динамической линковкой для целей. Без Tuist принятие такого решения несколько усложняется, поскольку необходимо вручную настраивать граф зависимостей. Однако благодаря Tuist Projects это больше не проблема.
Мы рекомендуем использовать динамические библиотеки или фреймворки во время разработки с помощью
аксессоров пучков, чтобы отделить логику доступа к пучкам от природыбиблиотеки или фреймворка целевого объекта. Это важно для ускорения компиляции и обеспечения надежной работы SwiftUI Previews. А статические библиотеки или фреймворки для релизных сборок обеспечат быструю загрузку приложения. Вы можете использовать
динамическую конфигурацию для изменения типа продукта во время генерации:bash
# You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest}
# and use it to change the linking type {#and-use-it-to-change-the-linking-type}
TUIST_PRODUCT_TYPE=static-library tuist generateswift
// You can place this in your manifest files or helpers
// and use the returned value when instantiating targets.
func productType() -> Product {
if case let .string(productType) = Environment.productType {
return productType == "static-library" ? .staticLibrary : .framework
} else {
return .framework
}
}СОВРЕМЕННЫЕ БИБЛИОТЕКИ
Apple попыталась облегчить неудобство переключения между статическими и динамическими библиотеками, введя объединяемые библиотеки. Однако это вносит недетерминизм во время сборки, что делает вашу сборку невоспроизводимой и сложной для оптимизации, поэтому мы не рекомендуем использовать этот метод.
Код
TMA не имеет никакого отношения к архитектуре кода и паттернам для ваших модулей. Однако мы хотели бы поделиться некоторыми советами, основанными на нашем опыте:
- Использование компилятора - это здорово. Чрезмерное использование компилятора может оказаться непродуктивным и привести к ненадежной работе некоторых функций Xcode, таких как предварительный просмотр. Мы рекомендуем использовать компилятор для внедрения хороших практик и раннего обнаружения ошибок, но не до такой степени, чтобы усложнять чтение и сопровождение кода.
- Экономно используйте макросы Swift. Они могут быть очень мощными, но также могут сделать код более сложным для чтения и сопровождения.
- Примите платформу и язык, не абстрагируйтесь от них. Попытки придумать сложные уровни абстракции могут оказаться контрпродуктивными. Платформа и язык достаточно мощные, чтобы создавать отличные приложения без дополнительных слоев абстракции. Используйте хорошие паттерны программирования и проектирования в качестве ориентира для создания своих функций.
Ресурсы
- Building µFeatures
- Фрейм-ориентированное программирование
- [Путешествие в рамочку и Свифт] (https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift)
- Использование фреймворков для ускорения разработки на iOS - часть 1
- Библиотечно-ориентированное программирование
- Построение современных фреймворков
- [Неофициальное руководство по файлам xcconfig] (https://pewpewthespells.com/blog/xcconfig_guide.html)
- Статические и динамические библиотеки
