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

Я решил, что время, затрачиваемое на запуск проекта, можно уменьшить, а основную часть процесса можно автоматизировать. При этом я собрал лучшие практики и инструменты, которые мы использовали, и подготовил шаблон проекта, который мы используем при запуске новых проектов. Такой шаблон должен сэкономить время разработчика на конфигурацию нового проекта, а также обеспечить общую структуру проекта, к которой привыкнет каждый член команды, чтобы больше не приходилось думать и изучать структуру нового для него проекта. Теперь это всегда одинаково.

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

Cocoapods


Я не думаю, что Cocoapods требует представления. Это библиотека для управления внешними зависимостями для IOS проектов. Она существует уже давно и является надежной и проверенной на практике в тысячах (если не в миллионах) проектов. Также существуют и альтернативные менеджеры зависимостей, например: Carthage, но я решил продолжить с Cocoapods, потому что у нее самый широкий спектр поддерживаемых проектов с открытым исходным кодом. Использовать Cocoapods очень просто, и он предоставляется с search index, который позволяет легко находить пакеты, которые могут пригодиться в любой момент.

Проект шаблона предоставлен с простым Podfile, который содержит Swiftlint и R.swift. Шаблон также включает Gemfile для управления версией Cocoapods, используемой для управления зависимостями. Данное решение является часто игнорируемым разработчиками, но оно предотвращает проблемы, возникающие, когда разработчики Вашей команды устанавливают зависимости, используя различные версии Cocoapods. Gemfile принудительно использует одну и ту же версию Cocoapods для всей команды.

Swiftlint


Swiftlint — это очень полезный инструмент для обеспечения соблюдения определенных правил и стиля написания кода каждым разработчиком в одной команде. Данную систему можно было бы рассматривать, как автоматизированную систему проверки кода, которая предупреждает разработчика об опасных моментах, таких, как, например, force casts, force tries и т. д., но кроме вышесказанного, данная система применяет общий стиль написания кода, следя за тем, чтобы все разработчики следовали одинаковым правилам, связанным со «стилем кода», например: отступы или интервалы. Такой подход имеет огромные преимущества, не только выполняя экономию времени на проверку кода, но также делает файлы проекта узнаваемыми, что повышает их читабельность и, как следствие, их понимание всеми участниками команды разработчиков. По данной ссылке предоставлен список всех правил. В шаблоне, Swiftlint он устанавливается посредством Cocoapods и включается в шаг «Фазы компиляции», поэтому он покажет предупреждение разработчику при каждой компиляции проекта.

R.swift


R.swift — это инструмент для получения строго типизированных, автоматически заполненных ресурсов, таких как изображения, сегменты шрифты и локализации. Он выполняет все перечисленные действия путем сканирования проекта и создания классов, необходимых для получения ресурсов. Самым большим преимуществом этой библиотеки является то, что при использовании ресурсов она делает программный код:

  • Fully typed — меньше приведений и предположений о том, какой метод будет возвращен
  • Compile time checked — больше не существует не корректных строк, которые приводят к остановке работы приложения во время выполнения кода
  • Autocompleted — отсутствие необходимости угадывать заново название image/nib/storyboard

Рассмотрим следующий код с использованием официального строкового API:

let icon = UIImage(named: “custom-icon”)

Если вы ошиблись в названии изображения, вы получите ноль. Если какой-либо член вашей команды изменит имя ресурса изображения, этот код вернет ноль или его выполнение будет остановлено, если вы принудительно развернете изображение. При использовании R.swift это выглядит так:

let icon = R.image.customIcon()

Теперь вы можете быть уверены, что пиктограмма действительно существует (компилятор предупредит вас, если это не так, благодаря проверкам времени компиляции), и Вы будете явно уверены, что не сделаете опечатку в названии пиктограммы, поскольку будете использовать автозаполнение.

R.swift устанавливается посредством Cocoapods и интегрируется в шаблон в Фазе компиляции и будет генерировать классы оболочки Swift для каждой компиляции. Это означает, что если вы добавите файл / изображение / локализацию / шрифт / цвет / перо и т. д., то все это будет доступно посредством R.swift после компиляции проекта.

Отдельный файл AppDelegate для тестов


Очень часто забывают о хорошей практике использования отдельного класса TestAppDelegate при запуске тестов. Почему это хорошая идея? Обычно класс AppDelegate выполняет много работы при запуске приложения. Он может иметь логику конфигураций window, конфигурировать базовое отображение пользовательского интерфейса приложения, выполнить логику регистрации для получения уведомлений, иметь код настройки подключения к базе данных и даже иногда выполнять вызовы серверного API. Юнит тесты не должны иметь сайд эффектов. Вы действительно не хотите выполнять вызовы случайных API и настраивать всю структуру пользовательского интерфейса вашего приложения только для запуска юнит тестов?

TestAppDelegate также является подходящим выбором для того, чтобы получить код, который вы хотите запускать только один раз во время выполнения набора тестов. Он может содержать код, который генерирует мок-объекты, заглушки сетевых запросов и т. д.

Шаблон содержит файл main.swift, который является основной точкой входа для приложения. В этом файле есть методы, которые проверяют среду работы приложения, и, если это тестовая среда, они вызывают TestAppDelegate.

Флаги профилирования производительности компилятора


Swift — это отличный язык, более простой в использовании и более безопасный, чем Objective-C (IMO). Но когда он был впервые представлен, у него был один большой недостаток — время компиляции. Вернувшись в дни, когда я работал над проектом в Swift, в котором было примерно 40 тыс. строк кода Swift (проект среднего размера). Код был очень тяжелым, с параметрами настройки и выводом типов, а компиляция чистой сборки заняла почти 5 минут. При внесении даже небольших изменений, проект повторно компилируется и это занимает около 2 минут, чтобы увидеть изменения. Это было одним из худших событий, которые я когда-либо испытывал, и из-за этого я почти прекратил использовать Swift.

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

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

Dev/Staging/Production configurations


Еще один хороший подход (или, можно сказать, необходимость) — иметь отдельную конфигурацию и переменные среды для сред разработки, финального тестирования и публикаций приложения. В настоящее время почти каждое приложение должно работать с сервером, и обычно такие сервера развертываются в нескольких средах. Среда разработки используется для ежедневного развертывания разработчиками для тестирования своего кода. Среда финального тестирования используется для создания стабильных релизов или для тестирования тестировщиками и клиентами.

Одним из способов поддержки нескольких сред в проекте iOS является добавление конфигураций на уровне проекта.

image
Конфигурации уровня проекта

Определив конфигурации, можно создать файл Configuration.plist, содержащий переменные для каждой среды.

image
Configuration.plist

При запуске проекта имеется возможность указывать, какую конфигурацию следует использовать. Вы можете сделать это в схеме сборки.

image

Затем необходимо добавить еще одно свойство в файл проекта Info.plist. Значение данного свойства будет динамически подставляться во время выполнения в имя текущей конфигурации.

image
Все это предварительно настроено в шаблоне.

Осталось только написать класс, который может извлекать эти переменные во время выполнения в зависимости от конфигурации, выбранной в схеме сборки. Шаблон содержит класс ConfigurationManager, который может извлекать переменные для текущей среды. Также можно проверить реализацию данного класса на Github, чтобы увидеть, как он работает.

Readme


Каждый проект должен содержать файл Readme, в котором, по крайней мере, содержатся инструкции по установке зависимостей и запуску проекта. Он также должен содержать описания архитектуры проекта и модулей. К сожалению, разработчики не любят писать документацию (файл readme является частью этой работы), и я видел проект, который разрабатывался месяцами, и у него даже был файл Readme. Чтобы снять бремя написания файла readme, шаблон содержит стандартный файл readme, который охватывает установку и структуру проекта. При настройке нового проекта с использованием шаблонов, вы автоматически включаете файл Readme.

Gitignore


В настоящее время большинство проектов используют GIT в качестве системы контроля версий. При использовании GIT у разработчиков нет желания игнорировать некоторые файлы или папки в проекте, например папку сборки или папку производных данных. Чтобы избавить разработчика от необходимости поиска файла gitignore, подходящего для его проекта iOS, шаблон содержит стандартный gitignore, предоставленный участниками Github.

Базовые классы для обработки внешних ссылок и уведомлений


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

Вывод


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

Комментарии (14)


  1. iStaZzzz
    29.10.2019 13:53
    +1

    Лучшие практики

    Обычно класс AppDelegate выполняет много работы при запуске приложения. Он может настроить окно, построить базовую структуру пользовательского интерфейса приложения, выполнить регистрацию для получения уведомлений, настроить базу данных и даже иногда выполнять вызовы API для определенной службы серверного приложения


    1. yarmolchuk Автор
      29.10.2019 14:51

      Автор статьи не говори, что это хорошо, он говорит что иногда такое встречается… и как решать такую проблему когда вы пишите юнит тесты…


      1. iStaZzzz
        29.10.2019 15:33

        Решать проблему нужно в корне. Борьба с последствиями и игнорирование причины и есть плохая практика.


        1. yarmolchuk Автор
          29.10.2019 15:55

          Согласен на все 100%… но суть статьи не в том… если Вы не заметили. Автор статьи не говорит про рефакторинг и как правильно организовать код в AppDelegate.


          1. iStaZzzz
            29.10.2019 17:55

            а говорит что к одному жуткому AppDelegate нужно добавить еще один — тестовый.


            1. IgorFedorchuk
              31.10.2019 22:21

              В тестовом AppDelegate не будет много логики, он будет подключен только к тестовому таргету и из основного проекта его не видно.


              1. iStaZzzz
                01.11.2019 14:28

                В том то и дело что будет. Человек один раз уже ошибся, а ему вместо решения предлагают сделать ошибку в 2 раза больше. И при изменениях одного файла придется менять второй. Или не прийдется. В общем радость от поддержки в полной мере.
                На претензию что такого не будет: будет.
                Все в курсе про объекты-Боги, о SOLID. Но перегруженные AppDelegate все равно создаются.


  1. debug45
    29.10.2019 17:21

    «Работая над разработкой»…


  1. Gargo
    29.10.2019 23:39
    -1

    Swiftlint — это очень полезный инструмент для обеспечения соблюдения определенных правил и стиля написания кода каждым разработчиком в одной команде.

    знаете что общего у swiftlint и коммунизма? Правильно — недостижимые лозунги.
    В реальности получается, что внедривший swiftlint, сам же задает правила написания кода и заставляет плясать всех под свою дудку. А окружающим приходится мириться с его правилами — даже если правила откровенно дурацкие. Например, в моем случае были:
    — имя переменной должно содержать не менее 3 букв (внедривший упорно не хотел принимать то, что переменные могут обозначать координаты точки (x, y) или что одной буквой обзывают переменные в циклах)
    — а давайте экономить место и поэтому порежем все пробелы в пустых строках. А заодно еще запретим делать несколько пустых строк подряд, и плевать на то, что кому-то захочется таким образом выделить кусок кода
    — "//MARK:-" низя писать без пробела
    — длина строки не больше ~100 символов
    — вишенка на торте — проект тупо не компилируется, если где-то какое-то условие swiftlint не соблюдено. Благо в особо запущенных случаях swiftlint можно отключить макросами


    1. alexwillrock
      30.10.2019 06:09

      Видимо, вы недавно пришли в командную разработку.
      Смысл линтера — чтобы весь код имел консистентный внешний вид, нейминг etc. Если каждый будет писать так, как ему вздумается — мы получим хаотичные текстовые файлы, и для изменения каждого придется долго вникать.
      Про количество переменных — уже давненько не используются циклы вида for(x;y;z), писать дженерики вида тоже не очень ясно или замыкания в виде T -> (where: { v -> x in }) тоже не передает сути.
      Проект не соберется до тех пор, пока не будут исправлены все ошибки линтера — это дополнительная гарантия того, что только после успешного билда на CI будет влит данный PR и он не противоречит командным правилам.


      1. Gargo
        30.10.2019 09:03

        уже давненько не используются циклы вида for(x;y;z)

        воу воу полегче. А что вы тогда делаете, если у вас 2 массива, объекты которых связаны через индекс?
        писать дженерики вида тоже не очень ясно или замыкания в виде T -> (where: { v -> x in }) тоже не передает сути

        booksArr.forEach { book in ...} — вспомнилась шутка Азазина про то, как некоторые люди считают других идиотами и любят тыкать в лицо очевидными фактами. У вас же либо T уже имеет более-менее вменяемое название, либо использующий ее код описывает ее назначение, либо назначение переменной настолько общее, что становится сложно выделить общие признаки и придумать к этому название


  1. Gargo
    01.11.2019 16:58

    еще пытался понять, зачем использовать gemfile, но так и не смог. Кроме как указывать версию cocoapods применений не нашел, но даже и это применение сомнительное — никогда не видел проблем, возникающих из-за разных версий cocoapods

    еще исправьте пожалуйста цвет текста в консоли — команда, генерирующая проект с помощью cookiecutter принудительно устанавливает зеленый цвет текста


    1. iWheelBuy
      01.11.2019 17:31

      Еще fastlane туда можно положить


    1. yarmolchuk Автор
      01.11.2019 17:38

      Все зависит от количества разработчиков на проекте и сложности проекта. Часто бывает, что нужно четко завязывайся на определенную версию pod.