@ObservedObject — е property wrapper в SwiftUI, който позволява на View да наблюдава промени в ObservableObject, създаден на друго място в йерархията. За разлика от @StateObject, @ObservedObject не създава обект — той само се абонира за неговия publisher objectWillChange и прерисува View при актуализиране на @Published свойства. Това прави @ObservedObject правилния избор за дъщерни View, които получават данни от родителя чрез инициализатор. Според статията на Paul Hudson — Hacking with Swift (2025), типичната архитектура на SwiftUI приложение се изгражда така: кореновото View използва @StateObject за създаване на view model, а всички дъщерни View го получават чрез @ObservedObject, което осигурява единен източник на истина без дублиране на данни.
Основни точки
@ObservedObject — е property wrapper, който абонира View за промените в ObservableObject. Когато обект, маркиран с @ObservedObject, промени някое от своите свойства, декларирани с @Published, SwiftUI автоматично прерисува View. @ObservedObject не създава обект — той само установява връзка между вече съществуващ екземпляр на ObservableObject и View, което трябва да реагира на неговите промени.
Ключовата разлика между @ObservedObject и @StateObject — е собствеността. @ObservedObject предполага, че обектът е създаден и съхранен някъде по-нагоре в йерархията на View. Дъщерното View получава референция към този обект чрез инициализатор и просто го наблюдава. Ако дъщерното View бъде пресъздадено, то ще получи същата референция от родителя — данните няма да бъдат загубени.
Според Apple Developer Documentation — SwiftUI (2025), @ObservedObject е достъпен от iOS 13, което го прави единствената опция за наблюдение на ObservableObject в проекти, поддържащи стари версии на iOS. В iOS 14+ се предпочита @StateObject за създаване на обекти, но @ObservedObject остава актуален за предаване на съществуващи обекти.
Механизмът на @ObservedObject се основава на протокола ObservableObject от Combine framework. Всеки клас, съответстващ на ObservableObject, автоматично получава publisher objectWillChange, който изпраща сигнал преди промяна на което и да е @Published свойство. SwiftUI се абонира за този publisher чрез @ObservedObject и при получаване на сигнала маркира View като изискващо прерисуване.
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// извличане на данни
isLoading = false
}
}
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel
var body: some View {
List(viewModel.tasks) { task in
Text(task.title)
}
.task { await viewModel.loadTasks() }
}
}
Когато родителското View предава viewModel на TaskListView чрез инициализатор, SwiftUI създава връзка между обекта и View. При промяна на масива tasks или флага isLoading, SwiftUI прерисува TaskListView. Обектът остава непроменен — той се съхранява в родителското View чрез @StateObject.
Разликата между @ObservedObject и @StateObject — е разликата между наблюдател и собственик. @StateObject създава обект и управлява неговия жизнен цикъл. @ObservedObject само наблюдава обект, който е създаден и съхранен на друго място. Изборът между тях се определя от отговорността на View за данните.
| Сценарий | Препоръка | Причина |
|---|---|---|
| View създава данни | @StateObject | View притежава обекта и отговаря за неговия жизнен цикъл |
| View получава данни | @ObservedObject | View само наблюдава, обектът живее в родителя |
| Поддръжка на iOS 13 | @ObservedObject | @StateObject недостъпен, използвайте @ObservedObject с ръчно управление |
| Компонент за многократна употреба | @ObservedObject | Компонентът не трябва да създава данни — получава ги отвън |
Основно правило: ако View създава обект — @StateObject. Ако View приема обект — @ObservedObject. Нарушаването на това правило в посока @ObservedObject за създаване на обект води до загуба на данни при възстановяване на View. Нарушаването в посока @StateObject за приемане на обект създава дублиран екземпляр, независим от родителския.
Типичен сценарий за използване на @ObservedObject — списък със задачи, където кореновото View създава view model, а клетката на списъка го получава чрез @ObservedObject. Всяка клетка може да извиква методи на view model-а и промените автоматично се показват в целия списък, тъй като всички клетки наблюдават един и същ обект.
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("Готово") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
В този пример TaskListContainer създава viewModel чрез @StateObject, а всеки TaskRow го получава чрез @ObservedObject. Когато потребителят кликне „Done” във всеки ред, viewModel.completeTask променя published свойството и всички View, наблюдаващи този обект, се актуализират автоматично.
Най-честата грешка — използване на @ObservedObject за създаване на обект вътре в View. Когато View бъде възстановено (например при промяна на state), SwiftUI създава нов екземпляр на ObservableObject, което води до загуба на всички натрупани данни. Тази грешка е особено болезнена в NavigationStack, където потребителят може да попълни формуляр и да загуби данни при връщане назад.
// ❌ Загуба на данни: @ObservedObject не запазва обекта
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// Нов formVM се създава при всяко възстановяване на View!
}
// ✅ Правилно: @StateObject запазва обекта
struct FormView: View {
@StateObject var formVM = FormViewModel()
// Обектът е създаден веднъж за времето на живот на View
}
Ако дъщерното View декларира същия ObservableObject чрез @StateObject, то създава независимо копие. Промените в родителския обект няма да бъдат видими в дъщерния и обратно. Винаги използвайте @ObservedObject за дъщерни View, които получават обекта отвън.
В съвременния SwiftUI съществуват няколко алтернативи на @ObservedObject, всяка със своите предимства. Изборът зависи от архитектурата на приложението, версията на iOS и конкретния сценарий на използване.
Изборът между @ObservedObject и @EnvironmentObject — е въпрос на стил и архитектура. @ObservedObject изрично показва зависимостите на View чрез инициализатор, което прави кода по-предвидим. @EnvironmentObject е удобен за дълбоки йерархии, но скрива зависимостите, което може да затрудни отстраняването на грешки.
Често задавани въпроси
Да, ако обектът се създава и съхранява извън SwiftUI — например в AppDelegate или сингълтон. В този случай @ObservedObject просто се абонира за промените на съществуващия обект. Въпреки това, за обекти, създадени вътре в SwiftUI йерархията, винаги е необходим @StateObject някъде по-нагоре.
Най-вероятната причина — свойството се променя не чрез @Published или не се променя самият обект, а неговата вътрешна структура без извикване на objectWillChange. За колекции използвайте присвояване на ново копие: array.append() не е достатъчно — трябва да присвоите целия масив наново чрез array = array + [element].
@ObservedObject сам по себе си не създава значително натоварване. Проблеми възникват при чести промени на @Published свойства — всяка промяна задейства прерисуване на всички наблюдаващи View. За оптимизация използвайте EquatableView, намалете броя на published свойствата и избягвайте ненужни актуализации.
@ObservedObject наблюдава целия клас ObservableObject и прерисува View при всяка промяна на неговите published свойства. @Binding създава двупосочна връзка с конкретна стойност (String, Int, Bool) и позволява четене и записването ѝ. @Binding е по-лек и не изисква ObservableObject.
Да, това е стандартен модел. @Published вътре в ObservableObject автоматично се интегрира с @ObservedObject. Всяко @Published свойство добавя наблюдател към publisher objectWillChange. При промяна на което и да е от тях, всички View с @ObservedObject за този обект се прерисуват.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също