@Observable и фреймворк Observation
Когда состояние сложнее флажка — это целая модель с логикой — на сцену выходит макрос @Observable из фреймворка Observation, представленного в iOS 17.
Суть урока: @Observable — современная замена ObservableObject. Достаточно пометить класс макросом, и SwiftUI сам отследит, какие именно свойства читает каждое вью, перерисовывая только нужное.
@State хорош для простых данных. Но модель приложения — например, корзина с товарами и методами — это класс с логикой, который должны видеть несколько экранов. Раньше для этого использовали ObservableObject с @Published. С iOS 17 пришёл фреймворк Observation и макрос @Observable, который проще и быстрее:
import Observation
@Observable
class Cart {
var items: [String] = []
var total = 0
func add(_ item: String, price: Int) {
items.append(item)
total += price
}
}Никаких @Published над каждым свойством — макрос делает класс наблюдаемым целиком. Во вью такую модель хранят через знакомый @State (для объекта, которым владеет вью):
struct CartView: View {
@State private var cart = Cart()
var body: some View {
VStack {
Text("Итого: \(cart.total)") // следим за total
Button("Добавить кофе") {
cart.add("Кофе", price: 200)
}
}
}
}Ключевое преимущество перед старым подходом — точечное отслеживание. С ObservableObject изменение любого @Published-свойства перерисовывало все наблюдающие вью. С @Observable SwiftUI отслеживает, какие именно свойства читает каждое вью, и обновляет только те, что действительно зависят от изменения. Меньше лишних перерисовок — выше производительность.
Если дочернему вью нужно создавать binding к свойству наблюдаемой модели, используется @Bindable:
struct EditView: View {
@Bindable var cart: Cart
var body: some View {
// $cart.total теперь доступно как binding
Stepper("Сумма: \(cart.total)", value: $cart.total)
}
}ObservableObject (старое) @Observable (iOS 17+)
меняем любое @Published меняем total
| |
перерисовка ВСЕХ перерисовка ТОЛЬКО вью,
наблюдателей что читают total
(избыточно) (точечно, быстрее)Попробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:
# Имитируем точечное отслеживание: фиксируем, какие поля читает вью.
class Cart:
def __init__(self):
self.items = []
self.total = 0
def add(self, item, price):
self.items.append(item)
self.total += price
class View:
def __init__(self, cart, reads):
self.cart = cart
self.reads = reads # какие поля важны этому вью
def needs_redraw(self, changed_field):
return changed_field in self.reads
cart = Cart()
total_view = View(cart, {'total'})
items_view = View(cart, {'items'})
cart.add('Кофе', 200)
print('total_view перерисовать?', total_view.needs_redraw('total')) # True
print('items_view перерисовать?', items_view.needs_redraw('total')) # FalseКак работает под капотом
Макрос @Observable на этапе компиляции переписывает класс: оборачивает доступ к свойствам так, что чтение регистрируется в текущем контексте отрисовки, а запись уведомляет зависящие вью. SwiftUI ведёт учёт «вью X прочло свойство total», и при изменении total планирует перерисовку только X. Это и есть тонкое отслеживание зависимостей. Поскольку @Observable работает с классами (ссылочный тип), модель естественно разделяется между экранами.
Частые ошибки
- Тянуть старый @StateObject/@Published. С iOS 17 предпочтителен @Observable и обычный @State.
- Помечать @Observable структуру. Макрос рассчитан на классы (ссылочную семантику).
- Использовать @State для модели, общей у многих экранов без передачи. Передавайте объект явно или через окружение.
Best practices
- Для моделей с логикой используйте @Observable-классы, для локального состояния — @State со значением.
- Читайте во вью только нужные свойства — так точечное отслеживание работает максимально эффективно.
- Для bindings к свойствам модели применяйте @Bindable.
Итоги. @Observable из фреймворка Observation — современный, быстрый способ управлять сложным разделяемым состоянием. Он заменяет ObservableObject, убирает @Published и перерисовывает только то, что реально изменилось. Это рекомендованный подход для приложений на iOS 17 и новее.
Шире контекста
Появление фреймворка Observation и макроса @Observable в iOS 17 — это веха, заметно упростившая жизнь iOS-разработчикам. Старый подход с ObservableObject, @Published, @StateObject и @ObservedObject был многословным и грубым: любое изменение перерисовывало всех наблюдателей, даже тех, кому изменённое свойство было безразлично. Новый макрос делает класс наблюдаемым целиком, без аннотаций над каждым свойством, и даёт тонкое отслеживание зависимостей на уровне отдельных свойств. Это не только удобнее писать, но и заметно быстрее на сложных экранах. Если вы встретите в старых руководствах ObservableObject — знайте, что для проектов на iOS 17 и новее современный путь это @Observable плюс обычный @State для владения объектом. Этот же объект легко передавать вниз по дереву явно или через окружение, что делает его идеальным местом для бизнес-логики, отделённой от представления.