Контроль над ресурсами. Настраиваем SwiftGen
Наверное, в каждом большом iOS-проекте-долгожителе можно наткнуться на иконки, которые нигде не используются, или обращения к ключам локализации, которые уже давно не существуют. Чаще всего такие ситуации возникают из-за невнимательности, а лучшее лекарство от невнимательности — автоматизация.
В iOS-команде HeadHunter мы большое внимание уделяем автоматизации рутинных задач, с которыми может столкнуться разработчик. Этой статьей мы хотим начать цикл рассказов о тех инструментах и подходах, которые упрощают нашу повседневную работу.
Какое-то время назад нам удалось взять ресурсы приложения под контроль с помощью утилиты SwiftGen. О том, как ее настроить, как с ней жить и как эта утилита помогает переложить проверку актуальности ресурсов на плечи компилятора, и пойдет речь под в статье.
SwiftGen — это утилита, которая позволяет генерировать Swift-код для доступа к различным ресурсам Xcode-проекта, среди них:
- шрифты;
- цвета;
- сториборды;
- строки локализации;
- ассеты.
Подобный код инициализации изображений или строк локализации мог писать каждый:
Для обозначения названия изображения или ключа локализации мы используем строковые литералы. То, что написано между двойными кавычками, никак не валидируется компилятором или средой разработки (Xcode). В этом кроется следующий набор проблем:
- можно сделать опечатку;
- можно забыть обновить использование в коде после редактирования или удаления ключа/изображения.
Давайте посмотрим, как мы можем улучшить подобный код с помощью SwiftGen.
Для нашей команды была актуальна генерация только для строк и ассетов, про них и пойдет речь в статье. Генерация для остальных типов ресурсов аналогична и при желании легко осваивается самостоятельно.
Внедрение в проект
Для начала нужно установить SwiftGen. Мы выбрали его установку через CocoaPods как удобный способ распространения утилиты между всеми участниками команды. Но это можно сделать и другими способами, которые детально описаны в документации. В нашем случае все, что нужно сделать, — это добавить в Podfile pod 'SwiftGen'
, после чего добавить новую фазу сборки (Build Phase), которая будет запускать SwiftGen перед началом сборки проекта.
"$PODS_ROOT"/SwiftGen/bin/swiftgen
Важно выполнять запуск SwiftGen перед запуском фазы Compile Sources
, чтобы избежать ошибок при компиляции проекта.
Теперь можно приступить к адаптации SwiftGen под наш проект.
Настройка SwiftGen
Первым делом необходимо настроить шаблоны, по которым будет генерироваться код для доступа к ресурсам. Утилита уже содержит набор шаблонов для генерации кода, их все можно посмотреть на гитхабе и, в принципе, они готовы к использованию. Шаблоны пишутся на языке Stencil, возможно, вы с ним знакомы, если пользовались Sourcery или играли с Kitura. При желании каждый из шаблонов можно адаптировать под свой стиль кода.
Для примера возьмем шаблон, который генерирует enum
для доступа к строкам локализации. Нам показалось, что в стандартном слишком много лишнего и его можно упростить. Упрощенный пример с поясняющими комментариями можно посмотреть в демо проекте.
Сам файл шаблона удобно сохранить в корне проекта, например в папке SwiftGen/Templates
, чтобы этот шаблон был доступен всем, кто работает над проектом.
Утилита поддерживает настройку через YAML-файл swiftgen.yml
, в котором можно указать пути до исходных файлов, шаблонов и дополнительные параметры. Создадим его в корне проекта в папке Swiftgen
, в эту же папку позже сгруппируем и другие файлы, связанные со скриптом.
Для нашего проекта этот файл может выглядеть так:
По сути, там указаны пути до файлов и шаблонов, а также дополнительные параметры, которые передаются в контекст шаблона.
Так как файл лежит не в корне проекта, нам нужно указать путь до него при запуске Swiftgen. Изменим наш скрипт запуска:
"$PODS_ROOT"/SwiftGen/bin/swiftgen config run --config SwiftGen/swiftgen.yml
Теперь наш проект можно собрать. После сборки в папке проекта по указанным в swiftgen.yml
путям должны появиться два файла Localization.swift
и Image.swift
. Их нужно добавить в Xcode-проект. Пример кода, который был сгенерирован с помощью SwiftGen также можно найти в демо проекте: Localization.swift и Image.swift.
Теперь можно заменить все использования строк локализации и инициализации изображений вида UIImage(named: "")
на то, что у нас сгенерировалось. Это упростит нам отслеживание изменений в ключах строк локализаций или их удаление. В любом из этих случаев проект просто не соберется, пока все ошибки, связанные с изменениями, не будут исправлены.
После изменений наш код выглядит так:
Настройка проекта в Xcode
Есть одна проблема с генерируемыми файлами: их можно изменить вручную по ошибке, а так как они перезаписываются с нуля при каждой компиляции, эти изменения могут быть утеряны. Чтобы этого избежать, можно блокировать файлы на запись после исполнения скрипта SwiftGen
.
Этого можно добиться с помощью команды chmod
. Перепишем нашу Build Phase
с запуском SwiftGen следующим образом:
Скрипт получается довольно простой. Перед запуском генерации, если файлы существуют, мы выдаем для них права на запись. После выполнения скрипта мы блокируем возможность изменения файлов.
Для удобства редактирования и проверки скрипта на ревью удобно вынести его в отдельный файл runswiftgen.sh
. Итоговый вариант скрипта с небольшими модификациями можно посмотреть здесь. Теперь наша Build Phase
будет выглядеть следующим образом: на вход скрипту передаем путь до папки Pods:
"$SRCROOT"/SwiftGen/runswiftgen.sh "$PODS_ROOT"
Пересобираем проект, и теперь при попытке изменения генерируемого файла вручную появится предупреждение:
Итак, папка со Swiftgen теперь содержит файл конфигурации, скрипт для блокирования файлов и запуска Swiftgen
и папку с настроенными шаблонами. Удобно добавить ее в проект для дальнейшего редактирования при необходимости.
А так как файлы Localization.swift
и Image.swift
генерируются автоматически, их можно добавить в .gitignore, чтобы лишний раз не решать в них конфликты после git merge
.
Отдельно хочется обратить внимание, на необходимость указывать Input/Output Files для билд фазы SwiftGen, если в вашем проекте используется Xcode New Build System (она является системой сборки по умолчанию с Xcode 10). В списке Input Files необходимо указать все файлы, на основе которых генерируется код. В нашем случае это сам скрипт, конфиг, шаблоны, файлы .strings и .xcassets:
$(SRCROOT)/SwiftGen/runswiftgen.sh
$(SRCROOT)/SwiftGen/swiftgen.yml $(SRCROOT)/SwiftGen/Templates/ImageAssets.stencil $(SRCROOT)/SwiftGen/Templates/LocalizableStrings.stencil $(SRCROOT)/SwiftGenExample/en.lproj/Localizable.strings $(SRCROOT)/SwiftGenExample/Assets.xcassets
В Output Files поместим файлы, в которых SwiftGen генерирует код:
$(SRCROOT)/SwiftGenExample/Image.swift $(SRCROOT)/SwiftGenExample/Localization.swift
Указание этих файлов необходимо чтобы система сборки могла решить нужно ли запускать скрипт, в зависимости от наличия или отсутствия изменений в файлах, и в какой момент сборки проекта этот скрипт должен быть выполнен.
Итоги
SwiftGen — хороший инструмент для защиты от нашей невнимательности при работе с ресурсами проекта. С его помощью у нас получилось автоматически генерировать код для доступа к ресурсам приложения и переложить часть работы по проверке актуальности ресурсов на плечи компилятора, а значит, немного упростить нашу работу. Помимо этого, мы настроили проект Xcode, чтобы дальнейшая работа с инструментом была более удобной.
Плюсы:
- Легче контролировать ресурсы проекта.
- Уменьшается вероятность опечаток, появляется возможность пользоваться автоподстановкой.
- Ошибки проверяются на этапе компиляции.
Минусы:
- Нет поддержки Localizable.stringsdict.
- Не учитываются ресурсы, которые не используются.
Полностью пример можно посмотреть на гитхабе
Оригинал статьи был опубликован на: https://habr.com.