Connect with us

Разработка

Автоматическое отслеживание изменений в UIKit и AppKit: функция, о которой Apple забыла упомянуть

Волшебство происходит, когда вы объединяете пользовательские трейты с наблюдаемыми объектами. Вы получаете автоматическое распространение И автоматические обновления.

Опубликовано

/

     
     

Помните, когда вышел SwiftUI, мы все удивлялись тому, как автоматически обновляются представления при изменении @Published свойств? Что ж, Apple тихо работает над тем, чтобы привнести эту же магию в UIKit и AppKit. А что самое лучшее? Она уже появилась в iOS 18/macOS 15, но о ней вряд ли кто-то знает. Вам даже не нужен Xcode 26, достаточно одной простой записи plist. Включите его с помощью ключа, и ваши представления волшебным образом обновятся при изменении ваших @Observable моделей. Больше никаких ручных вызовов setNeedsDisplay()!

Проблема, с которой мы все сталкивались

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

class ProfileViewController: UIViewController {
    var user: User? {
        didSet {
            updateUI()
        }
    }
    
    func updateUI() {
        nameLabel.text = user?.name
        avatarImageView.image = user?.avatar
        // ... 20 more lines of manual updates
        setNeedsLayout()
    }
}

Забыли вызвать updateUI()? Наслаждайтесь своим устаревшим интерфейсом. Вызывали его слишком часто? Привет, проблемы с производительностью. Это утомительно и подвержено ошибкам.

Появляется автоматическое отслеживание изменений

С новым фреймворком наблюдения весь этот шаблон становится устаревшим. Вот тот же код с автоматическим отслеживанием:

import Observation

@Observable
class User {
    var name: String = ""
    var avatar: UIImage?
    var unreadCount = 0
    
    var hasUnread: Bool {
        unreadCount > 0
    }
}

class ProfileViewController: UIViewController {
    let user = User()
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // UIKit tracks these property accesses automatically!
        nameLabel.text = user.name
        avatarImageView.image = user.avatar
        badgeView.isHidden = !user.hasUnread
    }
}

Вот и все. Измените user.name в любом месте вашего приложения, и метка обновится. Никаких ручных вызовов, никаких забытых обновлений, никаких накладных расходов на производительность из-за ненужных обновлений. Это просто работает.

Где работает отслеживание

Автоматическое отслеживание изменений поддерживается в различных методах UIKit и AppKit. В большинстве случаев viewWillLayoutSubviews() в контроллерах представлений UIKit, layoutSubviews() в представлениях UIKit и их эквиваленты AppKit (viewWillLayout() и layout()) являются лучшими вариантами.

Включаем волшебство

Вот где становится интересно. Эта функция не включена по умолчанию (пока). Вам нужно добавить ключ в ваш Info.plist:

Для UIKit (iOS 18+):

<key>UIObservationTrackingEnabled</key><true/>

Для AppKit (macOS 15+):

<key>NSObservationTrackingEnabled</key><true/>

Этот ключ plist включает отслеживание в iOS 18 и macOS 15. Начиная с 26-ой версии, это включено по умолчанию, и ключ будет просто игнорироваться.

iOS 26 и далее

iOS 26 (уже в бета-версии!) приносит улучшения. Новый метод updateProperties() как в UIView, так и в UIViewController обеспечивает еще лучшее место для доступа к наблюдаемым свойствам. Для полного обзора всех дополнений iOS 26 UIKit ознакомьтесь с превосходной статьей Джордана Моргана.

class MyView: UIView {
    let model: MyModel
    
    override func updateProperties() {
        super.updateProperties()
        // This runs before layoutSubviews for even better performance
        backgroundColor = model.backgroundColor
        layer.cornerRadius = model.cornerRadius
    }
}

Этот метод специально разработан для обновления свойств и запускается до layoutSubviews, что позволяет выполнять более эффективные обновления и более четкое разделение задач.

Так же, как в системе макетов есть setNeedsLayout() и layoutIfNeeded(), система обновления свойств предоставляет setNeedsUpdateProperties() и updatePropertiesIfNeeded(). Вы можете вызвать setNeedsUpdateProperties(), чтобы запланировать обновление свойств на следующий цикл обновления, или использовать updatePropertiesIfNeeded(), чтобы принудительно выполнить немедленное обновление, если оно ожидает обновления. Это дает вам точный контроль над тем, когда происходят обновления свойств, что особенно полезно для оптимизации производительности в сложных иерархиях представлений.

Документация по автоматическому отслеживанию свойств Apple содержит подробные рекомендации по использованию этих новых API. Кроме того, автоматическое отслеживание наблюдений включено по умолчанию в iOS 26, поэтому вам больше не понадобится ключ plist.

Ошибки

Конечно, не все так радужно. Вот несколько вещей, на которые следует обратить внимание:

  • Наблюдение происходит в определенных методах: отслеживаются только свойства, к которым осуществляется доступ в поддерживаемых методах
  • Время имеет значение: если вы выполняете дорогостоящие вычисления, рассмотрите возможность кэширования результатов, поскольку эти методы могут вызываться часто
  • Соображения относительно памяти: наблюдаемые объекты сохраняются во время наблюдения, поэтому помните о циклах удержания
  • Потокобезопасность: хотя @Observable является потокобезопасным, изменения из разных потоков могут привести к несогласованным представлениям пользовательского интерфейса. Сохраняйте все изменения в основном потоке, чтобы избежать сюрпризов

Шаблон, которого следует избегать

У вас может возникнуть соблазн создать метод, который предварительно обращается ко всем наблюдаемым свойствам:

// ❌ Don't do this
override func trackObservableProperties() {
    // Accessing all properties upfront
    _ = model.backgroundColor
    _ = model.cornerRadius
    _ = model.title
    // ... etc
}

Это антипаттерн по двум причинам:

  1. Неэффективность: он устанавливает зависимости наблюдения для ВСЕХ свойств, даже тех, которые не используются в текущем состоянии пользовательского интерфейса. Прелесть автоматического наблюдения в том, что оно отслеживает только свойства, к которым фактически осуществляется доступ во время обновлений.
  2. Хрупкость: вы поддерживаете дублирующийся список свойств, который может легко рассинхронизироваться с вашим фактическим кодом пользовательского интерфейса.

Вместо этого обращайтесь к свойствам напрямую там, где они используются:

// ✅ Do this
override func layoutSubviews() {
    super.layoutSubviews()
    // Only accessed properties create dependencies
    if model.isOptionEnabled {
        view.foo = model.bar  // Only bar is observed
    } else {
        view.foo = model.baz  // Only baz is observed
    }
}

Таким образом, только свойства, фактически влияющие на ваш пользовательский интерфейс, получают зависимости наблюдения, что делает ваш код и более эффективным, и более удобным для обслуживания.

Соображения по производительности

Вы можете задаться вопросом о производительности. Прелесть этой системы в том, что она отслеживает зависимости только тогда, когда представления фактически публикуются. Если представление не видно, отслеживания в нем нет. Фреймворк наблюдения использует сложный график зависимостей, который обеспечивает минимальные накладные расходы.

Полный пример проекта

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

Все фрагменты кода в этой статье взяты из полностью рабочего примера проекта. Посмотрите его на GitHub: ObservationTrackingExample

Недостающая часть: пользовательские трейты

Если вы использовали SwiftUI, вы знаете радость @EnvironmentObject — поместите объект в корень, получите к нему доступ откуда угодно. Разработчики UIKit годами завидовали этому шаблону. Ну, больше не завидуют (разработчики Mac упускают возможность — в AppKit пока нет эквивалента).

Начиная с iOS 17, UIKit тихо представил пользовательские трейты — способ прикреплять произвольные значения к коллекции трейтов, которая проходит через вашу иерархию представлений. Они больше не предназначены только для темного режима и классов размеров. У Кейта Харрисона есть отличное глубокое погружение в пользовательские трейты, если вам нужна полная история.

Волшебство происходит, когда вы объединяете пользовательские трейты с наблюдаемыми объектами. Вы получаете автоматическое распространение И автоматические обновления.

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: