Оле Бегеманн, 15 декабря 2022 г.
Я написал приложение под названием SwiftUI View Lifecycle. Приложение позволяет вам наблюдать, как различные конструкции и контейнеры SwiftUI влияют на жизненный цикл вью, включая время жизни его состояния и время вызова onAppear. Код приложения находится на GitHub. Его можно собрать для iOS и macOS.
Дерево вью и дерево рендеринга
Когда мы пишем код 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).
Я уверен, что вы найдете больше интересных вкусняшек, когда поиграете с приложением. Обратная связь приветствуется!