[Программирование, Разработка под iOS, Разработка мобильных приложений] SPM: модуляризация проекта для увеличения скорости сборки

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
11-Ноя-2020 15:31

Привет, Хабр! Меня зовут Эрик Басаргин, я 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 — он будет отвечать за доступ к палитре цветов, утвержденной дизайнером.
  • Создаем новый корневой модуль по инструкции выше.
  • Добавляем к нему корневые каталоги Scripts и Templates.
  • Добавляем в корень модуля файл Palette.xcassets и пропишем какие-либо color set'ы.
  • Добавляем пустой файл Palette.swift в Sources/Palette.
  • Добавим в папку Templates шаблон palette.stencil.
  • Теперь нужно прописать конфигурационный файл для 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
===========

Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_razrabotka_pod_ios (Разработка под iOS), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_surf, #_surfstudio, #_swift, #_ios, #_ios_development, #_ios_razrabotka (ios разработка), #_razrabotka_mobilnyh_prilozhenij (разработка мобильных приложений), #_spm, #_modular, #_architecture, #_modularization, #_macos, #_dependencies, #_swiftgen, #_blog_kompanii_surf (
Блог компании Surf
)
, #_programmirovanie (
Программирование
)
, #_razrabotka_pod_ios (
Разработка под iOS
)
, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 22-Ноя 23:59
Часовой пояс: UTC + 5