SwiftUI : Как @Observable заменяет @Published и ObservableObject

🔍 Введение

С выходом iOS 17 Apple представила новый макрос @Observable, который радикально упрощает работу с состоянием в SwiftUI. В этой статье мы разберём:

  • Как @Observable заменяет связку ObservableObject + @Published
  • Как теперь работать с @Environment
  • Как создавать биндинги через @Bindable
  • Какие подводные камни есть у withObservationTracking

🧱 Было: ObservableObject и @Published

final class Player: ObservableObject { @Published var score: Int = 0 } struct ContentView: View { @StateObject var player = Player() var body: some View { VStack { Text("Score: \(player.score)") Button("Increase") { player.score += 1 } } } }

✅ Стало: @Observable (iOS 17+)

@Observable final class Player { var score: Int = 0 } struct ContentView: View { @State var player = Player() var body: some View { VStack { Text("Score: \(player.score)") Button("Increase") { player.score += 1 } } } }

⚠ При использовании @Observable мы сохраняем объект с помощью @State, чтобы он не сбрасывался при перерисовке View.

📦 Передача через @Environment

Было: @EnvironmentObject

.environmentObject(player) @EnvironmentObject var player: Player

Стало: .environment() + @Environment(Type.self)

.environment(player) @Environment(Player.self) var player

SwiftUI сам определяет, какой тип объекта нужно искать в environment.

🔄 Проблема с биндингами

@Environment не даёт доступ к $player.score, и это вызывает ошибку:

TextField("Score", text: $player.score) // ❌ Cannot find $player

✅ Решение: @Bindable

@Environment(Player.self) var player var body: some View { @Bindable var bindable = player Stepper("Score", value: $bindable.score) }

🧠 Внутренности: withObservationTracking

Новая система работает через withObservationTracking:

withObservationTracking { print(player.score) } onChange: { // вызывается при изменении `score` }

Но! onChange вызывается до изменения (поведение willSet). Чтобы получить новое значение, нужен DispatchQueue.main.async.

withObservationTracking { print(player.score) } onChange: { DispatchQueue.main.async { observeAgain() } }

В отличие от Combine, нельзя просто подписаться на свойство и использовать .debounce, .map и другие операторы.

🔚 Заключение

Новый макрос @Observable делает SwiftUI ещё более декларативным. Он упрощает архитектуру, снижает количество обвязки и избавляет от зависимости на Combine. Но если вы работаете вне SwiftUI или с асинхронными задачами — ObservableObject пока может быть удобнее.

2
Начать дискуссию