День 19 — Observation и data flow
SwiftUI перерисовывает интерфейс, когда прочитанные им данные меняются. Observation framework из iOS 17 отслеживает доступ к properties точнее и требует меньше wrappers, чем старый ObservableObject.
Модель с @Observable
import Observation
@Observable
final class Cart {
var products: [String] = []
var draftName = ""
var isEmpty: Bool { products.isEmpty }
func addDraft() {
let name = draftName.trimmingCharacters(in: .whitespacesAndNewlines)
guard !name.isEmpty else { return }
products.append(name)
draftName = ""
}
}Macro @Observable генерирует observation-механику во время компиляции. Это не хранилище: сохранение данных проектируется отдельно.
Кто владеет состоянием
View, который создаёт модель и определяет срок её жизни, хранит её в @State:
struct CartScreen: View {
@State private var cart = Cart()
var body: some View {
CartEditor(cart: cart)
}
}@State сохраняет объект между обновлениями body. Не создавайте Cart() внутри body: при новой оценке View состояние может потеряться.
Когда нужен @Bindable
Обычной property достаточно для чтения и вызова methods. @Bindable нужен, когда дочерний экран должен получить Binding к property observable-модели.
struct CartEditor: View {
@Bindable var cart: Cart
var body: some View {
TextField("Товар", text: $cart.draftName)
Button("Добавить", action: cart.addDraft)
}
}Если черновик относится только к одному экрану, его можно оставить локальным @State. Не каждое временное значение должно жить в shared model.
Environment для зависимости
@main
struct ShopApp: App {
@State private var cart = Cart()
var body: some Scene {
WindowGroup {
ContentView()
.environment(cart)
}
}
}
struct CartBadge: View {
@Environment(Cart.self) private var cart
var body: some View {
Text("\(cart.products.count)")
}
}Environment подходит для настоящей shared dependency. Для локальной связи родителя с ребёнком явный параметр обычно проще.
Старый и новый подход
ObservableObject, @Published, @StateObject и @ObservedObject нужны для targets ниже iOS 17 и существующих libraries. В новом iOS 17+ коде начинайте с @Observable, @State для владельца и @Bindable только для bindings.
Глубже: изоляция интерфейса
Изоляция интерфейса
UI state меняется на main actor. Network и CPU work могут выполняться вне UI, а готовый результат присваивается модели на main actor. Mutable observable class нельзя передавать между concurrent tasks как будто она автоматически Sendable.
Checkpoint
Условие
Создайте @Observable-модель списка покупок, root View-владельца и editor с @Bindable. Добавьте computed property количества незавершённых пунктов и empty state.
Что нужно знать
- ownership через
@State; - bindings через
@Bindable; - derived state вместо дублирующей stored property;
@Environmentтолько для shared dependency.
Показать подсказку
Сначала сделайте модель независимой от SwiftUI. View должен читать pendingCount, а не вручную обновлять второй счётчик.
Показать решение
Проверьте три сценария: пустой список, добавление элемента и отметка выполненным. После каждого действия pendingCount должен вычисляться из массива.
Комментарии
Вопросы, находки и подсказки по этой странице.
Войди, чтобы задать вопрос или помочь другим.