Урок 1.2 Анатомия и жизненный цикл приложения

Урок 1.2 Анатомия и жизненный цикл приложения

В предыдущем разделе вы узнали о различных стадиях жизненного цикла контроллера представления. Жизненный цикл приложения работает примерно так же: приложение открывается, работает, переходит в фоновый режим, а затем закрывается.

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


Что вы узнаете

  • Как выполнять код на разных стадиях жизненного цикла приложения

Терминология

  • активное (active)
  • делегат приложения (app delegate)
  • делегат сцены (scene delegate)
  • передний план (foreground)
  • состояние приложения (app state)
  • фон (background)

Связанные ресурсы

  • Приложение и среда
  • Управление жизненным циклом вашего приложения
  • Справочник API: UISceneDelegate
  • Справочник API: UIApplicationDelegate

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

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

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

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

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

Состояние приложения Описание
Не запущено Приложение не было запущено или было завершено, либо пользователем, либо системой.
Неактивно Приложение работает на переднем плане, но не получает событий касания. (Тем не менее, оно может выполнять другой код.) Приложение обычно остается неактивным только кратко, когда переходит в другое состояние.
Активно Приложение работает на переднем плане и получает события. В активном состоянии на приложение не налагаются специальные ограничения, и оно должно быть отзывчивым к пользователю.
Фон Приложение выполняет код, но не отображается на экране. Когда пользователь выходит из приложения, система перемещает приложение в фоновое состояние на короткое время перед приостановкой. В других случаях система может запустить приложение в фоновом режиме (или разбудить приостановленное приложение) и дать ему время для выполнения определенных задач. Например, система может разбудить приложение, чтобы оно могло обрабатывать фоновые загрузки, определенные типы событий местоположения, удаленные уведомления и другие события. Приложение в фоновом режиме должно выполнять как можно меньше работы.
Приостановлено Приложение находится в памяти, но не выполняет код. Система приостановит приложения, находящиеся в фоновом режиме и не имеющие незавершенных задач, и может удалить приостановленные приложения в любое время, не пробуждая их, чтобы освободить место для других приложений.

 

Разберем App Delegate

Создайте новый проект, используя шаблон iOS App, и назовите его "AppLifeCycle". При создании проекта убедитесь, что параметр интерфейса установлен на "Storyboard".

В Project Navigator откройте файл AppDelegate, который определяет класс AppDelegate. Класс AppDelegate соответствует протоколу UIApplicationDelegate, который определяет методы, служащие крючками для различных событий в жизненном цикле приложения.

Рассмотрите методы, включенные в app delegate. Прочтите комментарии, чтобы узнать, что делают эти методы. До iOS 13 у app delegate была гораздо большая роль в жизненном цикле приложения. В iOS 13 была введена возможность для приложений иметь несколько экземпляров, работающих на iPadOS, каждый из которых управляется UISceneDelegate.

Хотя можно обойтись без использования UISceneDelegate, вернувшись к использованию UIApplicationDelegate, как это было до iOS 13, лучше следовать новым паттернам проектирования. Таким образом, вы будете лучше подготовлены к работе с любыми будущими функциями, устройствами или устаревшими элементами, а также сможете расширить приложение для поддержки нескольких сцен на iPad, когда будете готовы. В этом курсе вы будете придерживаться семантики жизненного цикла на основе сцен, но все ваши приложения будут иметь только одну сцену—вы не будете делать ничего, чтобы явно поддерживать несколько сцен.

Завершение запуска

Когда ваше приложение завершило запуск, будет вызван первый метод, предоставляющий вам первую возможность запустить собственный код в приложении. Эта возможность не дублируется в scene delegate, поэтому вам нужно использовать app delegate для выполнения действий при первом запуске приложения.

func application(_ application: UIApplication,
   didFinishLaunchingWithOptions launchOptions:
   [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    return true
}

Конфигурация для подключения сессии сцены

Делегат приложения также управляет сценами по мере их подключения и отключения. Следующая функция вызывается, когда создается новая сессия сцены. По умолчанию Xcode генерирует ваш проект с Info.plist, который содержит информацию, необходимую для создания конфигурации сцены по умолчанию, названной “Default Configuration”, как видно из этой реализации. Это все, что вам нужно, чтобы начать – поддержка различных конфигураций сцен является продвинутой темой, которая здесь не будет рассмотрена.

func application(_ application: UIApplication,
   configurationForConnecting connectingSceneSession:
   UISceneSession, options: UIScene.ConnectionOptions) ->
   UISceneConfiguration {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the
       new scene with.
    return UISceneConfiguration(name: "Default Configuration",
       sessionRole: connectingSceneSession.role)
}

Did Discard Scene Sessions. Удаление сессий сцены

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

func application(_ application: UIApplication,
   didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was
       not running, this will be called shortly after
       application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were
       specific to the discarded scenes, as they will not return.
}

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

Анализ делегата сцены

В обозревателе проекта откройте файл SceneDelegate. Ваш делегат сцены отвечает за обработку компонентов уровня пользовательского интерфейса во время жизненного цикла вашего приложения. Эти методы будут вызываться для всех сцен, созданных вашим приложением. Вы заметите, что каждый метод получает экземпляр UIScene — это сцена, для которой произошло событие.

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

Will Connect To

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

func scene(_ scene: UIScene, willConnectTo session:
   UISceneSession, options connectionOptions:
   UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the
       UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will
       automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or
       session are new (see
       `application:configurationForConnectingSceneSession`
        instead).
    guard let _ = (scene as? UIWindowScene) else { return }
}

Scene Did Disconnect

Этот метод, вызываемый при удалении сцены из вашего приложения, является лучшим местом для очистки и освобождения любых ресурсов или сохранения файлов, необходимых для сцены. UIKit отключает сцены, когда пользователь явно закрывает их в переключателе приложений, и может отключать сцены в фоновом режиме от имени системы для освобождения ресурсов для других активных приложений. Этот метод не так часто переопределяется, как sceneDidEnterBackground(_:), описанный ниже. Сцена не гарантированно будет отключена, когда пользователь переключается на другое приложение.

func sceneDidDisconnect(_ scene: UIScene) {
    // Called as the scene is being released by the system.
    // This occurs shortly after the scene enters the background,
       or when its session is discarded.
    // Release any resources associated with this scene that can
       be re-created the next time the scene connects.
    // The scene may re-connect later, as its session was not
       necessarily discarded (see
       `application:didDiscardSceneSessions` instead).
}

Scene Did Become Active

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

func sceneDidBecomeActive(_ scene: UIScene) {
    // Called when the scene has moved from an inactive state to
       an active state.
    // Use this method to restart any tasks that were paused
      (or not yet started) when the scene was inactive.
}

Scene Will Resign Active

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

func sceneWillResignActive(_ scene: UIScene) {
    // Called when the scene will move from an active state to
       an inactive state.
    // This may occur due to temporary interruptions (ex. an
       incoming phone call).
}

Scene Will Enter Foreground

Этот метод вызывается как часть перехода из фона в состояние активного переднего плана — непосредственно перед sceneDidBecomeActive(_:). Вы можете использовать этот метод, чтобы отменить многие изменения, внесенные в вашу сцену при переходе в фоновое состояние.

func sceneWillEnterForeground(_ scene: UIScene) {
    // Called as the scene transitions from the background to
       the foreground.
    // Use this method to undo the changes made on entering
       the background.
}

Scene Did Enter Background

Следующий метод вызывается сразу после метода sceneWillResignActive(_:), когда сцена фактически переходит в фоновое состояние. Это подходящее время для остановки ресурсоемких процессов и сохранения работы или прогресса пользователя.

func sceneDidEnterBackground(_ scene: UIScene) {
    // Called as the scene transitions from the foreground
       to the background.
    // Use this method to save data, release shared resources,
       and store enough scene-specific state information
       to restore the scene back to its current state.
}

Попробуйте

Добавьте оператор print в каждый из методов делегата в AppDelegate и SceneDelegate, который описывает, что происходит, чтобы вызвать метод. Например, print("Application did finish launching.") в методе application(_:didFinishLaunchingWithOptions:) и print("Scene will resign active.") в методе sceneWillResignActive(_:).

Запустите приложение в симуляторе. Когда вы откроете приложение, вы увидите четыре сообщения, напечатанные в консоли:

Application did finish launching.
Scene will connect to session.
Scene will enter foreground.
Scene did become active.

Закройте приложение и вернитесь наглавный экран. Вы увидите, что на консоль выводятся еще два сообщения:

Scene will resign active.
Scene did enter background.

Откройте переключатель приложений и снова откройте приложение. Вы увидите еще два сообщения, напечатанные в консоли:

Scene will enter foreground.
Scene did become active.

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

Scene will resign active.
Scene did become active.

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

Какой метод мне использовать?

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

  • application(_:didFinishLaunchingWithOptions:)
  • sceneDidBecomeActive(_:)
  • sceneWillResignActive(_:)

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

 

Лабораторная работа — Подсчет событий в приложении

Цель

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

Создайте новый проект под названием "AppEventCount" с использованием шаблона iOS App. При создании проекта убедитесь, что параметр интерфейса установлен на "Storyboard".

Шаг 1

Добавление счетчиков событий в AppDelegate

  • Откройте файл AppDelegate и создайте две переменные в начале, чтобы считать количество запусков приложения и количество раз, когда была создана конфигурация для подключения к сцене.
var launchCount = 0
var configurationForConnectingCount = 0​

Увеличьте оба этих счетчика на 1 в соответствующих методах.

Шаг 2

Настройка вашего представления и контроллера представлений

  • Как и во всех приложениях, созданных с использованием шаблона iOS App, ваше приложение начинается с одного контроллера представлений. Перетащите семь меток в этот контроллер представлений, по одной для каждого из следующих семи методов жизненного цикла AppDelegate и SceneDelegate. Настройте необходимые ограничения. Подсказка: Рассмотрите возможность использования stack view. 
    • application(_:didFinishLaunchingWithOptions:)
    • application(_:configurationForConnecting:options:)
    • scene(_:willConnectTo:options:)
    • sceneDidBecomeActive(_:)
    • sceneWillResignActive(_:)
    • sceneWillEnterForeground(_:)
    • sceneDidEnterBackground(_:)
  • Вы будете использовать метки для отображения количества раз, когда каждое событие произошло. Создайте outlet для каждой метки, используя описательные имена, такие как didFinishLaunchingLabel и didBecomeActiveLabel.
  • Также для каждой метки, соответствующей событиям сцены (не событиям AppDelegate), создайте переменную для хранения количества вызовов метода делегата. Установите начальное значение каждой переменной в 0, как в следующем примере:

 

var willConnectCount = 0

Шаг 3

Доступ к переменным счетчика из AppDelegate

  • Чтобы обновить пользовательский интерфейс для вызовов application(_:didFinishLaunchingWithOptions:) и application(_:configurationForConnecting:options:), вам понадобится доступ к двум переменным, которые вы создали в AppDelegate. В ViewController добавьте следующую переменную:
var appDelegate = (UIApplication.shared.delegate as! AppDelegate)

Это позволит вам получить доступ к AppDelegate и вашим переменным счетчикам внутри ViewController. Ваш экземпляр AppDelegate известен как синглтон, и к нему можно получить доступ таким образом. Поскольку вы знаете, какого он типа, вы можете безопасно привести его к типу AppDelegate, используя as! AppDelegate.

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

  • Создайте метод updateView(), который обновляет каждую метку с текущими значениями счетчиков. Для событий AppDelegate вы будете получать доступ к переменным через новую переменную appDelegate, а для событий сцены вы будете использовать переменные экземпляра, которые вы создали внутри ViewController. Обновите метки в методе updateView(), как в следующем примере:
launchLabel.text = "The App has launched \
   (appDelegate.launchCount) time(s)"

Шаг 4

Дать SceneDelegate доступ к ViewController

  • В классе SceneDelegate, чуть ниже строки var window: UIWindow?, создайте переменную viewController типа ViewController?. Обязательно сделайте ее опциональной.
  • В начале метода scene(_:willConnectTo:options:) установите свойство viewController равным rootViewController. Это даст SceneDelegate доступ к экземпляру ViewController, позволяя вам писать код, который увеличивает соответствующие свойства счетчика в ViewController каждый раз, когда вызывается метод жизненного цикла приложения, как в следующем примере:
viewController = window?.rootViewController as? ViewController

Шаг 5

Увеличение значений свойств счетчика

  • В методе scene(_:willConnectTo:options:) увеличьте значение свойства счетчика ViewController, соответствующего подключению сцены:
viewController?.willConnectCount += 1
  • Повторите этот шаг для других методов SceneDelegate:
    • sceneDidBecomeActive(_:)
    • sceneWillResignActive(_:)
    • sceneWillEnterForeground(_:)
    • sceneDidEnterBackground(_:)

Шаг 6

Регулярно обновляйте вид

  • Ваш вид необходимо регулярно обновлять, чтобы отображать новые значения счетчиков для каждого события приложения. Вы могли бы подумать о вызове updateView() в методе viewDidLoad() вашего ViewController, но есть проблема: viewDidLoad() не будет вызываться при всех событиях приложения.

Есть более подходящее место для обновления вида. Один из методов событий сцены, sceneDidBecomeActive(_:), будет вызываться после каждого из других методов жизненного цикла и непосредственно перед тем, как пользователь снова сможет взаимодействовать со сценой. Метод sceneDidBecomeActive(_:) - это идеальное место для вызова метода updateView() контроллера вида, обеспечивая правильное отображение значений счетчиков на метках.

 

Шаг 7

Тестирование

  • Запустите приложение в Simulator.
  • Метки для событий "приложение завершило запуск" и "сцена стала активной" должны показывать 1 в своих счетчиках.
  • Перейдите на домашний экран в Simulator.
  • Вернитесь в приложение и посмотрите, какие другие метки изменились.
  • Откройте переключатель приложений, но вместо переключения на другое приложение вернитесь к тому же приложению.
  • Посмотрите, какие метки изменились.

Когда пользователь выходит из вашего приложения или закрывает сцену, вызывается метод sceneDidDisconnect(_:) — но добавление метки и счетчика таким же образом для этого метода будет неэффективным, так как данные будут потеряны между завершениями.

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

 

 

 

 


Отрывок из книги
Develop in Swift Data Collections
Apple Education
https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewBook?id=1581183203

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.