@ObservedObject — to property wrapper w SwiftUI, który pozwala View obserwować zmiany w ObservableObject utworzonym w innym miejscu hierarchii. W przeciwieństwie do @StateObject, @ObservedObject nie tworzy obiektu — on tylko subskrybuje jego publisher objectWillChange i przerysowuje View przy aktualizacji właściwości @Published. To czyni @ObservedObject właściwym wyborem dla podrzędnych View, które otrzymują dane od rodzica przez inicjalizator. Według artykułu Paul Hudson — Hacking with Swift (2025), typowa architektura aplikacji SwiftUI wygląda następująco: główny View używa @StateObject do utworzenia view model, a wszystkie podrzędne View otrzymują go przez @ObservedObject, co zapewnia jeden źródło prawdy bez duplikowania danych.
Najważniejsze
@ObservedObject — to property wrapper, który subskrybuje View do zmian ObservableObject. Gdy obiekt oznaczony @ObservedObject zmienia którąkolwiek ze swoich właściwości zadeklarowanych z @Published, SwiftUI automatycznie przerysowuje View. @ObservedObject nie tworzy obiektu — on jedynie ustanawia połączenie między już istniejącym egzemplarzem ObservableObject a View, które ma reagować na jego zmiany.
Kluczowa różnica między @ObservedObject a @StateObject — to własność. @ObservedObject zakłada, że obiekt został utworzony i przechowywany gdzieś wyżej w hierarchii View. Podrzędne View otrzymuje referencję do tego obiektu przez inicjalizator i po prostu go obserwuje. Jeśli podrzędne View zostanie odtworzone, otrzyma tę samą referencję od rodzica — dane nie zostaną utracone.
Według Apple Developer Documentation — SwiftUI (2025), @ObservedObject jest dostępny od iOS 13, co czyni go jedyną opcją do obserwacji ObservableObject w projektach obsługujących starsze wersje iOS. W iOS 14+ preferowany jest @StateObject do tworzenia obiektów, ale @ObservedObject pozostaje aktualny do przekazywania istniejących obiektów.
Mechanizm @ObservedObject opiera się na protokole ObservableObject z frameworka Combine. Każda klasa zgodna z ObservableObject automatycznie otrzymuje publisher objectWillChange, który wysyła sygnał przed zmianą dowolnej właściwości @Published. SwiftUI subskrybuje tego publishera przez @ObservedObject i po otrzymaniu sygnału oznacza View jako wymagające przerysowania.
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// pobierz dane
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() }
}
}
Gdy nadrzędne View przekazuje viewModel do TaskListView przez inicjalizator, SwiftUI tworzy połączenie między obiektem a View. Przy zmianie tablicy tasks lub flagi isLoading SwiftUI przerysowuje TaskListView. Obiekt pozostaje przy tym niezmieniony — jest przechowywany w nadrzędnym View przez @StateObject.
Różnica między @ObservedObject a @StateObject — to różnica między obserwatorem a właścicielem. @StateObject tworzy obiekt i zarządza jego cyklem życia. @ObservedObject tylko obserwuje obiekt, który został utworzony i przechowywany w innym miejscu. Wybór między nimi jest określony przez odpowiedzialność View za dane.
| Scenariusz | Zalecenie | Przyczyna |
|---|---|---|
| View tworzy dane | @StateObject | View posiada obiekt i odpowiada za jego cykl życia |
| View otrzymuje dane | @ObservedObject | View tylko obserwuje, obiekt żyje w rodzicu |
| Wsparcie iOS 13 | @ObservedObject | @StateObject niedostępny, użyj @ObservedObject z ręcznym zarządzaniem |
| Komponent wielokrotnego użytku | @ObservedObject | Komponent nie powinien tworzyć danych — otrzymuje je z zewnątrz |
Główna zasada: jeśli View tworzy obiekt — @StateObject. Jeśli View przyjmuje obiekt — @ObservedObject. Naruszenie tej zasady w stronę @ObservedObject do tworzenia obiektu prowadzi do utraty danych przy odbudowie View. Naruszenie w stronę @StateObject do przyjmowania obiektu tworzy zduplikowany egzemplarz, niezależny od nadrzędnego.
Typowy scenariusz użycia @ObservedObject — lista zadań, gdzie główne View tworzy view model, a komórka listy otrzymuje go przez @ObservedObject. Każda komórka może wywoływać metody view modelu, a zmiany są automatycznie wyświetlane na całej liście, ponieważ wszystkie komórki obserwują ten sam obiekt.
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("Gotowe") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
W tym przykładzie TaskListContainer tworzy viewModel przez @StateObject, a każda TaskRow otrzymuje go przez @ObservedObject. Gdy użytkownik kliknie „Done” w dowolnym wierszu, viewModel.completeTask zmienia właściwość published, a wszystkie View obserwujące ten obiekt aktualizują się automatycznie.
Najczęstszy błąd — użycie @ObservedObject do tworzenia obiektu wewnątrz View. Gdy View jest odbudowywane (np. przy zmianie state), SwiftUI tworzy nowy egzemplarz ObservableObject, co prowadzi do utraty wszystkich zgromadzonych danych. Ten błąd jest szczególnie dotkliwy w NavigationStack, gdzie użytkownik może wypełnić formularz i stracić dane przy powrocie.
// ❌ Utrata danych: @ObservedObject nie przechowuje obiektu
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// Nowy formVM tworzony przy każdej odbudowie View!
}
// ✅ Poprawnie: @StateObject przechowuje obiekt
struct FormView: View {
@StateObject var formVM = FormViewModel()
// Obiekt utworzony raz na czas życia View
}
Jeśli podrzędne View deklaruje ten sam ObservableObject przez @StateObject, tworzy niezależną kopię. Zmiany w nadrzędnym obiekcie nie będą widoczne w podrzędnym i odwrotnie. Zawsze używaj @ObservedObject dla podrzędnych View, które otrzymują obiekt z zewnątrz.
We współczesnym SwiftUI istnieje kilka alternatyw dla @ObservedObject, każda z własnymi zaletami. Wybór zależy od architektury aplikacji, wersji iOS i konkretnego scenariusza użycia.
Wybór między @ObservedObject a @EnvironmentObject — to kwestia stylu i architektury. @ObservedObject jawnie pokazuje zależności View przez inicjalizator, co czyni kod bardziej przewidywalnym. @EnvironmentObject jest wygodny dla głębokich hierarchii, ale ukrywa zależności, co może utrudnić debugowanie.
Często zadawane pytania
Tak, jeśli obiekt jest tworzony i przechowywany poza SwiftUI — na przykład w AppDelegate lub singletonie. W tym przypadku @ObservedObject po prostu subskrybuje zmiany istniejącego obiektu. Jednak dla obiektów tworzonych wewnątrz hierarchii SwiftUI zawsze potrzebny jest @StateObject gdzieś wyżej.
Najbardziej prawdopodobna przyczyna — właściwość jest zmieniana nie przez @Published lub zmienia się nie sam obiekt, a jego wewnętrzna struktura bez wywołania objectWillChange. Dla kolekcji używaj przypisania nowej kopii: array.append() nie wystarczy — trzeba przypisać całą tablicę na nowo przez array = array + [element].
@ObservedObject sam w sobie nie tworzy znaczącego obciążenia. Problemy pojawiają się przy częstych zmianach właściwości @Published — każda zmiana wyzwala przerysowanie wszystkich obserwujących View. Do optymalizacji używaj EquatableView, zmniejszaj liczbę właściwości published i unikaj zbędnych aktualizacji.
@ObservedObject obserwuje całą klasę ObservableObject i przerysowuje View przy każdej zmianie jego właściwości published. @Binding tworzy dwukierunkowe połączenie z konkretną wartością (String, Int, Bool) i pozwala ją czytać i zapisywać. @Binding jest lżejszy i nie wymaga ObservableObject.
Tak, to standardowy wzorzec. @Published wewnątrz ObservableObject automatycznie integruje się z @ObservedObject. Każda właściwość @Published dodaje obserwatora do publishera objectWillChange. Przy zmianie dowolnej z nich wszystkie View z @ObservedObject dla tego obiektu są przerysowywane.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również