Понимание жизненных циклов вью SwiftUI

27 декабря 2022

Оле Бегеманн, 15 декабря 2022 г.

Я написал приложение под названием SwiftUI View Lifecycle. Приложение позволяет вам наблюдать, как различные конструкции и контейнеры SwiftUI влияют на жизненный цикл вью, включая время жизни его состояния и время вызова onAppear. Код приложения находится на GitHub. Его можно собрать для iOS и macOS.

iPhone screenshots of the SwiftUI View Lifecycle app

Mac screenshot of the SwiftUI View Lifecycle app

Дерево вью и дерево рендеринга

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

Дерево вью служит светокопией, из которого SwiftUI создает второе дерево, которое представляет «объекты» фактического вью, который находится «на экране» в любой момент времени («объекты» могут быть объектами фактического UIView или NSView, а также другими представлениями; точное значение «на экране» может варьироваться в зависимости от контекста). Крис Эйдхоф любит называть это второе дерево деревом рендеринга (ссылка указывает на 3-минутное видео, где Крис демонстрирует эту двойственность, настоятельно рекомендуется).

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

 

Жизненные циклы и состояние вью

Мы можем определить время жизни вью как промежуток времени, в течение которого оно существует в дереве рендеринга. Время жизни начинается с вставки в дерево рендеринга и заканчивается удалением. Важно отметить, что время жизни распространяется на состояние вью, определенное с помощью @State и @StateObject: когда вью удаляется из дерева рендеринга, его состояние теряется; когда вью будет снова вставлено позже, состояние будет воссоздано с его начальным значением.

Приложение SwiftUI View Lifecycle отслеживает три события жизненного цикла вью и отображает их в виде меток времени:

  • @State = когда было создано состояние вью (эквивалентно началу жизни вью)
  • onAppear = когда onAppear последний раз вызывался
  • onDisappear = когда onDisappear последний раз вызывался

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

Приложение позволяет наблюдать эти события в различных контекстах. Просматривая примеры, вы заметите, что время этих событий меняется в зависимости от контекста, в который встроено вью. Например:

  • Оператор if/else создает и уничтожает свои дочерние вью каждый раз, когда изменяется условие; состояние не сохраняется.
  • ScrollView с готовностью вставляет все свои дочерние элементы в дерево рендеринга, независимо от того, находятся они внутри области просмотра или нет. Все дочерние элементы появляются сразу и никогда не исчезают.
  • List с динамическим содержимым (с использованием ForEach) лениво вставляет только видимые в данный момент дочерние представления. Но как только время существования дочернего представления началось, список сохранит свое состояние, даже если он снова прокручивается за пределы экрана. onAppear и onDisappear вызываются повторно, когда представления прокручиваются в область просмотра и из нее.
  • NavigationStack вызывает onAppear и onDisappear как только вью отправлены и “всхлопнуты”. Состояние родительских уровней в стеке сохраняется при отправке дочернего вью.
  • TabView запускает жизненный цикл всех дочерних вью немедленно, даже невидимых вкладок. onAppear и onDisappear вызываются повторно, когда пользователь переключает вкладки, но вью вкладки поддерживает состояние для всех вкладок.

 

Уроки

Вот несколько уроков, которые можно из этого извлечь:

  •  Различные вью контейнеров могут иметь различное поведение производительности и использования памяти, в зависимости от того, как долго они поддерживают дочерние вью.
  • onAppear не обязательно вызывается при создании состояния. Это может произойти позже (но никогда раньше).
  • onAppear можно вызывать несколько раз в некоторых вью контейнеров. Если вам нужно, чтобы побочный эффект возникал ровно один раз за время существования вью, подумайте о том, чтобы написать себе хелпер onFirstAppear, как показано Иэном Кином и Джорданом Морганом в книге «Запуск кода только один раз в SwiftUI» (01.11.2022).

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

Содержание