Узнайте, когда SwiftUI перерисовывает подпредставления

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

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

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

Итак, мое любопытство защипало: что делает SwiftUI? Когда он перерисовывает свои компоненты? Я создал небольшой проект, чтобы попытаться понять это, и сегодня я хочу поделиться своим процессом и своими выводами.

Воссоздание приложения

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

  • BigCountNumber
  • Counter

Для простоты я поместил их в один файл:

Затем я изменил RenderingApp.swift, чтобы загрузить представление Counter вместо ContentView, чтобы я мог его протестировать.

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

Визуализируйте визуализацию

Второй шаг — понять, как визуализировать перерисовку компонента. Я обнаружил функцию static_printDebug() протокола View SwiftUI: закрытый метод, который можно использовать для отладки View SwiftUI при его рендеринге.

Внимание! не забудьте удалить вызов этой функции перед отправкой в ​​Apple. В противном случае ваше приложение будет автоматически отклонено: вы не можете использовать частные системные API в своем приложении.

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

Новый код выглядит так:

Запустив приложение сейчас, Xcode напечатал несколько сообщений в консоли. При запуске приложения Xcode печатает:

И, после нажатия на кнопку increment несколько раз, печатается:

Добавление украшения

React и SwiftUI в настоящее время ведут себя одинаково. В этой настройке изменение State аналогично изменению свойств задействованных компонентов.

Давайте добавим компонент Decoration, как это предлагается в справочной статье.

Чтобы отслеживать, когда отображается Decoration, я добавил код в _printChanges. Составим его с компонентом Counter, как показано в статье:

Запустив приложение, Xcode выводит следующий вывод.

Мы можем заметить, что Decoration появляется только один раз, в то время как Counter и BigCountNumber обновляются каждый раз, когда мы нажимаем на кнопку.

По умолчанию SwiftUI ведет себя более консервативно, чем React: он избегает ненужного рендеринга компонентов, чьи реквизиты не меняются.

Почему?

Мое лучшее объяснение касается семантики типов компонентов. В SwiftUI мы создаем Views, которые являются struct: по умолчанию неизменяемыми типами. Это значительно упрощает проверку, когда что-то меняется, а когда нет.

Если свойства View меняются, мы должны воссоздать его: в body View мы используем инициализаторы различных компонентов, которые создают View. По сути, мы говорим среде выполнения: «Смотрите, я создаю новое представление. Можешь нарисовать?».

React, с другой стороны, работает со ссылочными типами: компонентами класса или функции. Гораздо сложнее (и дороже) понять, когда что-то меняется: компоненты не создаются постоянно. React должен решить, когда снова вызывать функцию render (или функцию, которая определяет сам компонент). Когда переменная class изменяется — например, одна из его реквизитов — адрес памяти класса не меняется.

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

Заключение

В сегодняшней статье мы рассмотрели различия в рендеринге между React и SwiftUI. Мы узнали, как использовать приватную функцию _printChanges для отладки нашего процесса рендеринга, и создали экспериментальный проект, чтобы протестировать его.

Мы узнали, что SwiftUI перерисовывает свой компонент при изменении их свойств, а не при изменении состояния, как это делает React. Это возможно главным образом для значения семантики структур.