Меня зовут Дмитрий. Так получилось, что я являюсь тим лидом в команде из 13 iOS разработчиков уже на протяжении двух лет. И вместе мы трудимся над приложением Тинькофф Бизнес.  Хочу поделиться с вами нашим опытом о том, как релизить приложение в неожиданный момент с максимальным набором фич или баг фиксами и при этом не поседеть.  Расскажу о практиках и подходах которые помогли команде заметно ускориться в разработке и тестировании и заметно сократить количество стресса, багов, проблем при внеплановом или срочном релизе. #MakeReleaseWithoutStress. Представьте себе следующую ситуацию. Идет очередной релиз. Ему предшествовало регрессионное тестирование, тестировщики снова нашли место, в котором вместо текста в приложении отображается ID строки. Это была одна из самых частых наших проблем с которой мы сталкивались. Вы можете не столкнуться с данной проблемой, если у вас не локализовано приложение на другой язык, или вся локализация пишется строками прямо в коде без использования Localizable.strings файла. Но вы можете столкнуться с другими проблемами, которые мы поможем вам решить: Почему это все происходит? Есть программный код, который компилируется. Если вы что-то написали не так (синтаксически, или неправильное название функции при вызове), то ваш проект просто не соберется. Это понятно, очевидно и логично. А как быть с такими вещами, как ресурсы? Они не компилируются, просто добавляются в bundle уже после компиляции кода. В связи с этим может возникать большое количество проблем в runtime, например, тот случай, что описан выше — со строками в локализации. Мы задумались, как подобные проблемы решаются вообще, и как мы можем это исправить. Я вспомнил одну из конференций Cocoaheads в mail.ru. Там был доклад про сравнение инструментов для кодогенерации. Посмотрев еще раз, что эти инструменты (библиотеки/фреймворки) из себя представляют, мы наконец-то нашли то, что было нужно. При этом, похожий подход уже годами используется разработчиками под Android. Google подумал о них и сделал им такой инструмент из коробки. А ведь нам Apple даже стабильный Xcode не может сделать... Оставалось выяснить только одно — какой именно инструмент выбрать: Natalie, SwiftGen или R.swift? У Natalie не было поддержки локализации, от него было решено сразу отказаться. У SwiftGen и R.swift были очень похожие возможности. Мы сделали выбор в пользу R.swift, просто исходя из количества звезд, зная о том, что в любой момент мы можем поменять на SwiftGen.  Запускается pre-compile build phase скрипт, пробегает по структуре проекта и генерирует файл, под названием R.generated.swift, который нужно будет добавить в проект (о том, как это сделать мы детальнее расскажем в самом конце). Файл имеет следующую структуру: Использование: "Зачем нужен Rswift.StringResource?", — спросите вы. Я сам не понимаю, зачем его генерировать, но, как объясняют авторы, то нужен он для следующего: ссылка. Небольшое пояснение контента ниже: *Было — пользовались подходом какое-то время, в итоге, ушли от него
*Стало — подход который используем при написании нового кода
*Не было, но у вас может быть — подход, которого никогда не существовало в нашем приложении, но я встречал его в различных проектах, в те далекие времена, когда еще не работал в Tinkoff.ru. Мы начали применять R.swift для локализации, это избавило нас от проблем, о которых мы писали в самом начале. Теперь, если поменялся id в локализации, то проект не соберется. *Это работает только при условии, если вы поменяли id во всех локализациях на другой. Если же в какой-то из локализаций осталась строка, то при компиляции будет warning, что данный id локализован не на всех языках. Теперь, если мы что-то переименовали в *.xcassets, и не поменяли в коде, то проект просто не соберется. И так далее. R.validate() — это замечательный инструмент, который бьет по рукам (вернее просто выкидывает error в catch блок), если вы сделали что-то не так в storyboard или xib файлах.
Например: Использование: *Component-based system — wiki, концепция разработки кода, при которой компоненты (набор экранов/модулей связанных между собой) разрабатываются в замкнутой среде (в нашем случае в локальных подах) с целью уменьшения связанности кодовой базы. Многим известен подход в backend, который основан на данном концепте — микросервисы. *Монолит — wiki, концепция разработки кода, при которой вся кодовая база лежит в одном репозитории, и код тесно связан между собой. Данная концепция подходит маленьким проектам с конечным набором функций. Если вы разрабатываете монолитное приложение или используете только сторонние зависимости, то вам повезло (но это не точно). Берете tutorial и выполняете все строго по нему. Это был не наш случай. Мы втянулись. Так как мы используем component-based system, то, помимо встраивания R.swift в основное приложение, мы решили встраивать его еще и локальные поды (которые являются компонентами). Из-за постоянного обновления локализаций, картинок и всех элементов, которые влияют на файл R.generated.swift, возникает много конфликтов в сгенерируемом файле при мерже в общую ветку. И чтобы этого избежать, следует убрать R.generated.swift из под власти git репозитория. Автор так же рекомендует это делать. Добавляем в .gitignore следующие строки.  Еще, если вы не хотите генерировать код для каких-то ресурсов, всегда можно воспользоваться игнорированием отдельных файлов или целых папок: описание .rswiftignore Как и в основном проекте, нам было важно не добавлять R.generated.swift файлы из локальных подов в git репозиторий. Мы начали рассматривать варианты, как это можно было бы сделать: магия в Podfile Мы временно остановились на варианте: "магия в Podfile", при том, что у него был ряд недостатков: Живя какое-то время со скриптом и страдая, я решил поизучать эту тему шире и нашел еще один вариант.
В Podspec есть prepare_command, которая предназначена, как раз для создания и изменения исходников, которые затем будут добавлены в проект. *News — название пода, которое нужно заменить на название именно вашего локального пода
*touch — команда для создания файла. Аргументом является относительный путь до файла (включая название файла с расширением)  Далее мы будем производить махинации с News.podspec Данный скрипт вызывается при первом запуске pod install и добавляет нужный нам файл в папку исходников в поде. Далее идет еще один "финт ушами" — нам нужно сделать вызов скрипта R.swift для локальных подов. Правда, есть одно "но". C локальными подами prepare_command не работает, вернее работает, но в каких-то особых случаях. Есть обсуждение этой темы на Github. *Fatality — wiki, финальный удар в Mortal Kombat. Проведя еще немножечко ресерча, я нашел еще одно решение — гибрид подходов c prepare_command и pre_install. Небольшая модификация магии из Podfile: И тот же самый скрипт, что не запускался для локальных подов В итоге это работает так, как мы и ожидаем.  Наконец-то! P.S.: Пытался сделать еще кастомную команду вместо prepare_command, но pod lib lint (команда для валидации контента podspec и самого пода) ругается на лишние переменные и не проходит.  В удаленных подах (те, что находятся каждый в своем репозитории) не нужно всей этой скриптовой магии, что описано выше, ибо там кодовая база строго привязана к версии зависимости. Достаточно просто встроить в сам Example (проект, генерируемый после команды pod lib create <Name>) R.swift скрипт и добавлять R.generated.swift в пакет с библиотекой (подом). Если в проекте нет Example, то уже придется писать скрипты, которые будут похожи на те, которые я привел. P.S.: Есть небольшое уточнение:
R.swift + Xcode 10 + new build system + incremental build != <3
Подробнее о проблеме на главной странице библиотеки или тут
R.swift v4.0.0 не работает с cocoapods 1.6.0 :(
Думаю в скором времени уже поправят все проблемы. Всегда нужно держать планку качества как можно выше. Особенно это важно для приложений, которые работают с финансами. При этом не нужно перегружать тестирование и находить баги как можно раньше. В нашем случае это находится либо в момент компиляции кода разработчиком, либо на прогоне проверок для Pull Requests. Тем самым, отсутствие локализации мы находим не внимательным взглядом тестировщиков или автоматизированными тестами, а обычным процессом сборки приложения. Также нужно учитывать тот факт, что это — сторонний инструмент, который завязан на структуру проекта и парсит ее контент. Если поменяется структура файла проекта, то и инструмент придется менять.
Мы пошли на этот риск и, в случае чего, всегда готовы поменять данный инструмент на любой другой или написать свой. А выигрыш от R.swift — огромное количество человеко-часов, которые команда может потратить на куда более важные вещи: новые фичи, ресерч новых технический решений, повышение качества и так далее. R.swift сполна вернул то количество времени, которое было потрачено на его интеграцию, даже с учетом возможной замены его в будущем на другое похожее решение. R.swift Вы можете поиграться с примером, чтобы сразу своими глазами увидеть профит от кодогенерации для ресурсов. Исходный код проекта "на поиграться": GitHub. iOS Team Lead