WWDC25: Optimize SwiftUI performance with Instruments | Apple
Как улучшить производительность SwiftUI приложений?
Введение в проблемы производительности
- Приветствие от Джеда и Стивена, обсуждение важности анализа кода для выявления узких мест.
- Симптомы проблем с производительностью: задержки, зависания, проблемы с анимацией и прокруткой.
- Основное внимание будет уделено диагностике проблем производительности в коде SwiftUI.
Инструменты для диагностики
- Использование нового инструмента SwiftUI в Instruments 26 для выявления ненужных обновлений.
- Пример приложения "Landmarks", которое показывает расстояние до достопримечательностей и проблемы с плавностью прокрутки.
Новый инструмент SwiftUI
- Обзор нового инструмента SwiftUI, который помогает идентифицировать проблемы с производительностью.
- Включает Time Profiler для анализа работы CPU и инструменты Hangs and Hitches для отслеживания отзывчивости приложения.
Анализ данных профилирования
- Первоначальный анализ информации из инструмента SwiftUI: "Update Groups" показывает работу SwiftUI.
- Три ключевых типа обновлений: Long View Body Updates, Long Representable Updates и Other Long Updates. Эти категории помогают определить долгие обновления.
Начало профилирования приложения
- Установка Xcode 26 и обновление ОС на устройстве для записи трассировок SwiftUI.
- Процесс профилирования приложения "Landmarks": запуск Instruments через Xcode и выбор шаблона SwiftUI.
Исследование результатов профилирования
- Остановка записи после прокрутки списка достопримечательностей; данные обрабатываются для анализа.
- Проверка верхнего уровня длинных обновлений в инструменте SwiftUI; акцент на длительных обновлениях тела представления как основной причине проблем с производительностью.
Подробный анализ подкатегорий обновлений
- Расширение трека SwiftUI для просмотра подкатегорий: View Body Updates, Representable Updates и Other Updates.
- Иерархический обзор всех модулей обновлений тела представления во время сеанса профилирования.
Как оптимизировать обновления представлений в SwiftUI?
Фильтрация и анализ обновлений
- Для фильтрации длинных обновлений можно использовать выпадающее меню, выбрав "Long View Body Updates". Это позволяет увидеть количество длинных обновлений для дальнейшего анализа.
- Выбор опции "Show Updates" открывает последовательный список всех длинных обновлений для данного представления. Установка диапазона инспекции помогает сосредоточиться на конкретном интервале времени.
Профилирование производительности
- Time Profiler собирает данные о работе CPU, фиксируя текущие выполняемые функции и сохраняя эту информацию для профилирования. В панели деталей профиля отображаются стеки вызовов зафиксированных образцов.
- При расширении основного стека вызовов видно, что большая часть времени тратится на вычисляемое свойство
distance, которое вызывает два разных форматтера.
Оптимизация работы с форматтерами
- Свойство
distanceпреобразует расстояние до объекта в отформатированную строку. Создание форматтеров занимает много времени, что влияет на производительность приложения.
- Каждое выполнение тела представления требует ожидания завершения форматирования текста расстояния, что может замедлить обновление интерфейса пользователя.
Влияние времени выполнения на производительность
- Важно понимать, как работает цикл рендеринга: приложение обрабатывает события и затем обновляет интерфейс до истечения временного лимита кадра.
- Если одно из обновлений UI занимает слишком много времени, это может привести к задержкам (hitches), когда предыдущий кадр остается видимым дольше необходимого.
Решение проблемы с задержками
- Задержки делают анимацию менее плавной. Рекомендуется заранее рассчитывать строки расстояний и кэшировать их вместо выполнения этого во время работы тела представления.
- Перемещение вычисления строк расстояний в класс управления местоположением позволяет избежать затрат на создание новых форматтеров каждый раз при выполнении тела представления.
Кэширование данных для повышения эффективности
- Создание строк формата заранее и их кэширование обеспечит доступность уже рассчитанных значений при необходимости отображения в представлении.
- Инициализация форматтеров в классе LocationFinder позволит повторно использовать их без дополнительных затрат на создание новых экземпляров.
Управление обновлениями представлений в SwiftUI
Оптимизация обновлений расстояний
- В коде используется массив для хранения ориентиров и словарь для кэширования строк расстояний. Функция
updateDistancesпересчитывает строки при изменении местоположения.
- При обновлении местоположения необходимо также обновить кэш строк. Это делается в функции
didUpdateLocations, вызываемой CoreLocation.
Улучшение производительности приложения
- После внесенных изменений, долгие обновления представлений исчезли, что улучшило производительность приложения.
- Долгие обновления происходили только в начале трассировки, когда приложение готовилось отобразить первый кадр, что не вызывает задержек при прокрутке.
Проблемы с избыточными обновлениями
- Избыточные обновления представлений могут также негативно сказаться на производительности приложения.
- Много быстрых обновлений может привести к пропуску дедлайна на отправку кадра, что вызывает задержки.
Новая функция "Избранное"
- Добавлена кнопка "сердечко" для добавления и удаления объектов из избранного. Код реализует эту функциональность через метод
toggleFavorite.
- Модель хранит массив избранных объектов и добавляет или удаляет их по мере необходимости.
Анализ производительности новой функции
- Для проверки работы новой функции используется инструмент Instruments. Записываются действия пользователя при выборе объектов из списка.
- Ожидается, что нажатия кнопок "избранное" вызовут соответствующие изменения в представлении без лишних обновлений.
Отладка и понимание SwiftUI
- Обновления представлений LandmarkListItemView происходят чаще, чем ожидалось. Это требует дополнительной отладки.
- В отличие от UIKit, где можно использовать точки останова для анализа стека вызовов, SwiftUI сложнее анализировать из-за рекурсивных обновлений внутри AttributeGraph.
Как обновляются представления в SwiftUI?
Основы работы SwiftUI
- SwiftUI использует декларативный подход, что затрудняет понимание причин обновления представлений. Необходимо разобраться в механизме работы SwiftUI.
- Представления соответствуют протоколу View и реализуют свойство body для определения внешнего вида и поведения, возвращая другое значение View.
Модель данных и атрибуты
- При добавлении представления в иерархию, SwiftUI получает объект атрибута от родительского представления, который хранит структуру представления.
- Структуры представлений часто пересоздаются, но атрибуты сохраняют свою идентичность и состояние на протяжении всего времени жизни представления.
Обновление состояния
- Когда происходит изменение переменной состояния, SwiftUI создает новую транзакцию для обновления иерархии представлений перед следующим кадром.
- Транзакция помечает атрибут переменной состояния как устаревший. Затем SwiftUI проходит по цепочке зависимостей, помечая каждую из них как устаревшую.
Процесс обновления
- После завершения всех транзакций, SwiftUI определяет, что нужно нарисовать на экране. Он не может получить эту информацию из-за устаревших данных.
- Атрибуты обновляются до тех пор, пока все необходимые данные для отображения не будут актуализированы.
Граф причин и следствий
- Вопрос "почему запустилось мое тело представления?" сводится к вопросу "что отметило мое тело как устаревшее?" Это важно для контроля зависимостей между представлениями.
- Новый инструмент SwiftUI предоставляет график причин и следствий (Cause & Effect Graph), который показывает отношения между изменениями состояний и обновлением представлений.
Пример с приложением Landmarks
- Граф показывает цепочку причинно-следственных связей: изменения состояний из-за взаимодействия пользователя (например, нажатие кнопки).
- Узел графа LandmarkListItemView.body демонстрирует множество обновлений тела из-за изменений массива избранных объектов.
Проблемы с обновлением представлений в SwiftUI
Анализ проблемы с обновлением
- При нажатии на кнопку "Избранное" происходит обновление всех элементов на экране, а не только того, который был выбран. Необходимо разобраться в коде.
- Используется макрос @Observable для отслеживания изменений свойств в SwiftUI. Это создает зависимость между каждым элементом и массивом избранных объектов.
- Каждый элемент списка вызывает функцию isFavorite для определения, должен ли значок быть выделен. Это приводит к повторному выполнению тела каждого представления при изменении массива избранных.
Оптимизация зависимостей
- При добавлении нового избранного через кнопку toggleFavorite все элементы помечаются как устаревшие, что приводит к ненужным обновлениям.
- Для улучшения ситуации предлагается создать Observable view model для каждого представления, чтобы отслеживать статус избранного индивидуально.
Внедрение новых моделей
- Каждое представление будет иметь свою собственную модель, что позволит избежать зависимости от полного массива избранных.
- Теперь каждое представление может управлять своим состоянием без влияния на другие элементы интерфейса.
Результаты изменений
- После внедрения новых моделей количество обновлений уменьшилось до двух при изменении статуса двух избранных объектов.
- График причин и следствий показывает только два обновления вместо множества ненужных вызовов.
Работа с окружением в SwiftUI
Понимание окружения
- Значения окружения хранятся в структуре EnvironmentValues и создают зависимость между представлениями и окружающей средой приложения.
- При изменении любого значения окружения уведомляются все связанные представления о необходимости повторного выполнения их тела.
Обновления графа причин и следствий
- Существуют два типа узлов: внешние обновления окружения (например, изменение цветовой схемы системы).
- Если цветовая схема меняется из-за перехода устройства в темный режим, это отражается в графе как причина для запуска тела соответствующих представлений.
Оптимизация производительности SwiftUI
Влияние обновлений представлений на производительность
- Обновления представлений могут происходить одновременно, что облегчает их идентификацию. Даже если тело представления не требует выполнения из-за обновления окружения, все равно существует стоимость проверки обновлений значений, что может быстро накапливаться при большом количестве представлений.
- Избегайте хранения часто обновляющихся значений (например, геометрических значений или таймеров) в окружении. Граф причин и следствий помогает визуализировать поток данных в приложении и гарантирует, что ваши представления обновляются только по необходимости.
Лучшие практики для повышения производительности
- Необходимость минимизировать ненужные обновления тела представлений. Проектируйте поток данных так, чтобы ваши представления обновлялись только тогда, когда это действительно необходимо. Будьте особенно осторожны с зависимостями, которые изменяются очень часто.
- Используйте инструменты анализа (Instruments) на ранних этапах разработки для оценки производительности приложения. Основной вывод: обеспечьте быстрое обновление тел ваших представлений только по мере необходимости для достижения высокой производительности SwiftUI.
Дополнительные ресурсы
- Ознакомьтесь с документацией, упомянутой в описании видео, чтобы узнать о других функциях инструмента профилирования вашего приложения.