Почему в SwiftUI при использовании ObservableObject происходит перерасчет body, даже если значение не используется?
Что проверяют
Знаете ли вы, на что именно подписывается вью: на объект целиком или на конкретное свойство. От этого зависит, будете ли вы оптимизировать случайные места или разберётесь с источником обновлений.
Как отвечать
ObservableObject публикует ровно одно событие: objectWillChange. Обёртка @Published дёргает его в willSet и не сообщает, какое свойство изменилось. Вью, которая держит объект через @StateObject, @ObservedObject или @EnvironmentObject, подписана на этот общий сигнал, поэтому её body пересчитывается при изменении любого поля, даже не прочитанного.
Важная оговорка, которая отличает сильный ответ: пересчёт body это не перерисовка. SwiftUI сравнивает получившееся описание со старым и обновляет только изменившиеся узлы. Дорого становится тогда, когда внутри body есть сортировки, форматтеры или создание объектов.
Чем лечится:
- дробить модель на несколько мелких вместо одного объекта на весь экран, у каждого свои подписчики;
- опускать подписку вниз: родитель передаёт дочерней вью готовое значение, а на объект подписывается маленькая вью рядом с местом отрисовки;
- держать
bodyдешёвым, вынося вычисления в модель; - перейти на макрос
@Observableиз фреймворка Observation: там отслеживается чтение конкретных свойств, и вью обновляется, только если читала именно изменившееся.
Где спотыкаются
Ставят print в body, видят лишние срабатывания и чинят то, что не болит. Сначала стоит убедиться, что просадка есть, и посмотреть, что именно внутри дорогое.
Второе: пытаются локализовать обновления и создают модель прямо в инициализаторе дочерней вью под @ObservedObject. Вью это структура, её пересоздают часто, модель пересоздаётся вместе с ней и теряет состояние. Владение это @StateObject, наблюдение чужого объекта @ObservedObject.
Третье: уверены, что @Published уведомляет отдельно по каждому свойству. Из этого заблуждения растут попытки навести точность ручным objectWillChange.send(), хотя реально её дают только дробление модели или переход на Observation.