Русский
Русский
English
Статистика
Реклама

SPM модуляризация проекта для увеличения скорости сборки

Привет, Хабр! Меня зовут Эрик Басаргин, я iOS-разработчик в Surf.

На одном большом проекте мы столкнулись с низкой скоростью сборки от трёх минут и более. Обычно в таких случаях студии практикуют модуляризацию проектов, чтобы не работать с огромными монолитами. Мы в Surf решили поэкспериментировать и модуляризовать проект с помощью Swift Package Manager менеджера зависимостей от Apple.

О результатах расскажем в другой статье, а сейчас ответим на главные вопросы: зачем это всё нужно, почему мы выбрали SPM и как делали первые шаги.



Почему именно SPM


Ответ прост это нативно и ново. Он не создает overhead в виде xcworkspace, как Cocoapods, к примеру. К тому же SPM open-source проект, который активно развивается. Apple и сообщество исправляют в нем баги, устраняют уязвимости, обновляют вслед за Swift.

Делает ли это сборку проекта быстрее


Теоретически сборка ускорится из-за самого факта разделения приложения на модули framework'и. Это значит, что каждый модуль будет собираться только в том случае, когда в него внесли изменения. Но утверждать точно можно будет только по окончанию эксперимента.

Note: Эффективность модуляризации напрямую зависит от правильного разбиения проекта на модули.

Как сделать эффективное разбиение


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

Разделять обобщённую функциональность. Каждый модуль отвечает за какую-то категорию, к примеру:

  • CommonAssets набор ваших Assets'ов и public интерфейс для доступа к ним. Обычно он генерируется с помощью SwiftGen.
  • CommonExtensions набор расширений, к примеру Foundation, UIKit, дополнительные зависимости.

Разделять flow'ы приложения. Рассмотрим древовидную структуру, где MainFlow главное flow приложения. Представим, что у нас новостное приложение.

  • NewFlow экраны новостей и обзора конкретной новости.
  • FavoritesFlow экран со списком избранных новостей и экран обзора конкретной новости с дополнительным функционалом.
  • SettingsFlow экраны настроек приложения, аккаунта, категорий и т. д.

Выносить reusable компоненты в отдельные модули:

  • CommonUIComponents модуль, который содержит в себе любые небольшие UI-компоненты. Обычно они влезают в один файл.
  • Далее список бесконечен и зависит от количества внутренних кастомных UI компонентов. К примеру, нужно создать модули для генерации экранов результата, кастомной коллекции, билдера кастомных алертов и т. д.

Когда нужно выносить компонент в отдельный модуль


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

Такой подход решает проблему с потерей компонентов. Это происходит в больших проектах, и если компонент не задокументировали, то его поддержка и отладка впоследствии станет убыточной.

Создаём проект с использованием SPM


Рассмотрим создание тривиального тестового проекта. Я использую Multiplatform App project на SwiftUI. Платформа и интерфейс тут не имеют значения.

Note: Чтобы быстро создать Multiplatform App, нужен XCode 12.2 beta.

Создаём проект и видим следующее:



Теперь создадим первый модуль Common:

  • добавляем папку Frameworks без создания директории;
  • создаём SPM-пакет Common.



  • Добавляем поддерживаемые платформы в файл Package.swift. У нас это platforms: [.iOS(.v14), .macOS(.v10_15)]



  • Теперь добавляем наш модуль в каждый таргет. У нас это SPMExampleProject для iOS и SPMExampleProject для macOS.



Note: Достаточно подключать к таргетам только корневые модули. Они не добавлены как подмодули.

Подключение завершено. Теперь достаточно настроить модуль с public интерфейсом и вуаля, первый модуль готов.

Как подключить зависимость у локального SPM-пакета


Добавим пакет AdditionalInfo как Common, но без добавления к таргетам. Теперь изменим Package.swift у Common пакета.



Добавлять больше ничего не нужно. Можно использовать.

Пример, приближенный к реальности


Подключим к нашему тестовому проекту SwiftGen и добавим модуль Palette он будет отвечать за доступ к палитре цветов, утвержденной дизайнером.

  1. Создаем новый корневой модуль по инструкции выше.
  2. Добавляем к нему корневые каталоги Scripts и Templates.
  3. Добавляем в корень модуля файл Palette.xcassets и пропишем какие-либо color set'ы.
  4. Добавляем пустой файл Palette.swift в Sources/Palette.
  5. Добавим в папку Templates шаблон palette.stencil.
  6. Теперь нужно прописать конфигурационный файл для SwiftGen. Для этого добавим файл swiftgen.yml в папку Scripts и пропишем в нем следующее:

xcassets:  inputs:    - ${SRCROOT}/Palette/Sources/Palette/Palette.xcassets  outputs:    - templatePath: ${SRCROOT}/Palette/Templates/palette.stencil      params:        bundle: .module        publicAccess: true      output: ${SRCROOT}/Palette/Sources/Palette/Palette.swift


Итоговый внешний вид модуля Palette

Модуль Palette мы с вами настроили. Теперь надо настроить запуск SwiftGen, чтобы палитра генерировалась при старте сборки. Для этого заходим в конфигурацию каждого таргета и создаем новую Build Phase назовём ее Palette generator. Не забудьте перенести эту Build Phase на максимально высокую позицию.

Теперь прописываем вызов для SwiftGen:

cd ${SRCROOT}/Palette/usr/bin/xcrun --sdk macosx swift run -c release swiftgen config run --config ./Scripts/swiftgen.yml

Note: /usr/bin/xcrun --sdk macosx очень важный префикс. Без него при сборке вылетит ошибка: unable to load standard library for target 'x86_64-apple-macosx10.15.


Пример вызова для SwiftGen

Готово доступ к цветам можно получить следующим образом:
Palette.myGreen (Color type in SwiftUI) и PaletteCore.myGreen (UIColor/NSColor).

Подводные камни


Перечислю то, с чем мы успели столкнуться.

  • Всплывают ошибки архитектуры и портят всю логику разбиения на модули.
  • SwiftLint & SwiftGen не уживаются вместе при подгрузке их через SPM. Причина в разных версиях yml.
  • В крупных проектах не получится сразу избавиться от Cocoapods. А разбивать уже созданный проект с закреплёнными версиями подов настоящее испытание, потому что SPM только развивается и не везде поддерживается. Но SPM и Cocoapods более-менее работают параллельно: разве что поды могут кидать ошибку MergeSwiftModule failed with a nonzero exit code. Это происходит довольно редко, а решается очисткой и пересборкой проекта.
  • На данный момент SPM не позволяет прописать пути поиска библиотек. Приходится явно указывать их с завязкой на -L$(BUILD_DIR).

SPM замена Bundler?


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

SPM дает возможность вызывать swift run, если добавить Package.swift в корень вашего проекта. Что это нам дает? К примеру, можно вызвать fastlane или swiftlint. Пример вызова:

swift run swiftlint --autocorrect.
Источник: habr.com
К списку статей
Опубликовано: 11.11.2020 12:15:25
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании surf

Программирование

Разработка под ios

Разработка мобильных приложений

Surf

Surfstudio

Swift

Ios

Ios development

Ios разработка

Spm

Modular

Architecture

Modularization

Macos

Dependencies

Swiftgen

Категории

Последние комментарии

© 2006-2021, personeltest.ru