Прощай, AppDelegate

Долгое время разработчики iOS использовали AppDelegate в качестве основной точки входа для своих приложений. С запуском SwiftUI2 на WWDC 2020 Apple представила новый жизненный цикл приложения, который (почти) полностью устраняет AppDelegate, уступая место подходу, подобному DSL.

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

Указание точки входа в приложение

Один из первых вопросов, на который нам нужно ответить: Как мы можем сообщить компилятору о точке входа в наше приложение? SE-0281 определяет, как работают точки входа программы на основе типов:

«Компилятор Swift распознает тип, помеченный атрибутом @main, как предоставляющий точку входа для программы. Типы, отмеченные @main, имеют одно неявное требование: объявление статического main() метода ».

При создании нового приложения SwiftUI класс @main приложения выглядит следующим образом:

Так где же статическая функция main(), упомянутая в SE-0281?

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

Вот и все - это расширение протокола предоставляет реализацию по умолчанию, которая заботится о запуске приложения.

Поскольку фреймворк SwiftUI не является открытым исходным кодом, мы не можем увидеть, как Apple реализовала это, но Swift Argument Parser является открытым исходным кодом и также использует этот подход. Изучите исходный код ParsableCommand, чтобы узнать, как они используют расширение протокола для обеспечения реализации по умолчанию статической функции main, которая служит точкой входа в программу:

Если все это звучит немного сложно, хорошая новость заключается в том, что вам на самом деле не нужно беспокоиться об этом при создании нового приложения SwiftUI: просто не забудьте выбрать «SwiftUI App» в раскрывающемся списке Life Cycle при создании приложения, и вы сделали:

Давайте рассмотрим несколько распространенных сценариев.

Инициализация ресурсов / ваш любимый SDK или фреймворк

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

Обычно это делается с помощью метода ApplicationDelegate application(_:didFinishLaunchingWithOptions:). Поскольку у нас больше нет делегата приложения, нам нужно найти другие способы инициализировать наше приложение. В зависимости от ваших конкретных требований, вот несколько стратегий:

  • Реализуйте инициализатор в своем основном классе (см. Документацию)
  • Установить начальные значения для сохраненных свойств (см. Документацию)
  • Установите значения свойств по умолчанию с помощью закрытия (см. Документацию)

Если ничего из этого не соответствует вашим потребностям, вам может понадобиться ApplicationDelegate. Читайте до конца, чтобы узнать, как вы можете добавить его.

Управление жизненным циклом вашего приложения

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

Обычно вы используете applicationDidBecomeActive, applicationWillResignActive или applicationDidEnterBackground на своем ApplicationDelegate.

Начиная с iOS 14.0, Apple предоставила новый API, который позволяет более элегантно и удобно отслеживать состояние приложения: ScenePhase. В вашем проекте может быть несколько сцен, но есть вероятность, что у вас есть только одна сцена, обозначенная WindowGroup.

SwiftUI отслеживает состояние сцены в среде, и вы можете сделать текущее значение доступным для вашего кода, загрузив его с помощью оболочки свойства @Environment, а затем используя модификатор onChange(of:) для прослушивания любых изменений:

Стоит отметить, что вы также можете прочитать фазу из других мест в своем приложении. При чтении фазы на верхнем уровне приложения (как показано во фрагменте кода) вы получите совокупность всех фаз в вашем приложении. Значение .inactive означает, что ни одна из сцен в вашем приложении не активна.

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

Обработка глубоких ссылок

Раньше при обработке ссылок на контент вам приходилось реализовывать application(_:open:options:) и направлять входящий URL-адрес наиболее подходящему обработчику.

Это становится намного проще с новой моделью жизненного цикла приложения. Вы можете обрабатывать входящие URL-адреса, добавив модификатор onOpenURL к самой верхней сцене в вашем приложении:

Что действительно круто: вы можете установить несколько обработчиков URL-адресов в своем приложении, что значительно упростит создание ссылок на контент, поскольку вы можете обрабатывать входящие ссылки там, где это наиболее удобно.

Если возможно, вам следует использовать универсальные ссылки (или динамические ссылки Firebase, которые используют универсальные ссылки для приложений iOS), поскольку они используют связанные домены для создания связи между вашим веб-сайтом и вашим приложением. - это позволит вам безопасно обмениваться данными.

Однако вы по-прежнему можете использовать настраиваемые схемы URL-адресов для ссылки на контент в своем приложении.

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

$ xcrun simctl openurl booted <your url>

Продолжение активности пользователей

Если ваше приложение использует NSUserActivity для интеграции с Siri, Handoff или Spotlight, вам необходимо обрабатывать продолжение активности пользователя.

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

Вот фрагмент, который показывает, как рекламировать действие, например, в подробном представлении:

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

Помощь - ничего из вышеперечисленного мне не подходит!

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

Еще одна причина, по которой вам может потребоваться AppDelegate, заключается в том, что вы используете какие-либо сторонние SDK, которые используют переключение методов для внедрения в жизненный цикл приложения. Firebase - это всем известный случай.

Чтобы помочь вам, Swift предоставляет способ связать конформер AppDelegate с вашей App реализацией: @UIApplicationDelegateAdaptor. Вот как это использовать:

Не забудьте удалить атрибут @main, если вы копируете существующую AppDelegate реализацию, иначе компилятор будет жаловаться на несколько точек входа в приложение.

Заключение

При всем этом давайте обсудим, почему Apple внесла это изменение. Думаю, есть несколько причин:

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

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

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

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

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

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

Спасибо за прочтение!

Дальнейшее чтение

Если вы хотите узнать больше, ознакомьтесь с этими ресурсами: