@StateObject — to property wrapper w SwiftUI, który tworzy i jest właścicielem instancji ObservableObject przez cały cykl życia View. Gdy View pojawia się po raz pierwszy na ekranie, @StateObject inicjalizuje obiekt i przechowuje go, dopóki View nie zostanie usunięte z pamięci. Gwarantuje to, że dane nie zostaną zresetowane przy przebudowie interfejsu — na przykład przy zmianie motywu lub aktualizacji nadrzędnego View. Według Apple Developer Documentation (2025), @StateObject powinien być używany jako główne źródło prawdy (source of truth) dla ObservableObject w hierarchii SwiftUI, podczas gdy podrzędne View otrzymują już utworzony obiekt przez @ObservedObject lub @EnvironmentObject.
Najważniejsze
@StateObject — to property wrapper wprowadzony w iOS 14, który pozwala View tworzyć i posiadać instancję klasy zgodnej z protokołem ObservableObject. W przeciwieństwie do @State, który działa z typami wartościowymi (strukturami), @StateObject jest przeznaczony dla typów referencyjnych — klas, które mogą powiadamiać SwiftUI o zmianach swoich właściwości.
Gdy View używa @StateObject var viewModel: MyViewModel, SwiftUI automatycznie tworzy instancję MyViewModel przy pierwszym wyświetleniu View i przechowuje ją w specjalnym magazynie frameworka. Przy każdej aktualizacji View (na przykład przy zmianie nadrzędnego stanu) SwiftUI nie odtwarza obiektu — używa istniejącej instancji, dopóki View nie zostanie usunięte z hierarchii.
Według Apple WWDC Session 10137 (2024), @StateObject rozwiązuje problem utraty danych przy przebudowie View, który istniał w iOS 13, gdy programiści musieli tworzyć ObservableObject w nadrzędnym View i przekazywać go przez inicjalizator. Prowadziło to do powielania kodu i ryzyka przypadkowego odtworzenia obiektu.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Liczba: \(viewModel.count)")
Button("Zwiększ", action: viewModel.increment)
}
}
}
Mechanizm @StateObject opiera się na integracji SwiftUI z frameworkiem Combine. Gdy ObservableObject oznacza swoje właściwości atrybutem @Published, SwiftUI automatycznie subskrybuje zmiany przez publisher wbudowany w protokół ObservableObject. Przy zmianie opublikowanej właściwości obiekt wysyła sygnał przez publisher objectWillChange, co wyzwala przerysowanie wszystkich View obserwujących ten obiekt.
SwiftUI przechowuje instancję ObservableObject w specjalnym magazynie powiązanym z konkretną instancją View. Ten magazyn jest tworzony raz przy pierwszym renderowaniu i istnieje aż do zniszczenia View. Dlatego właśnie @StateObject gwarantuje stabilność referencji do obiektu — SwiftUI zarządza pamięcią automatycznie, nie polegając na inicjalizatorze View.
Według objc.io — Thinking in SwiftUI (2025), wewnętrzna implementacja @StateObject używa mechanizmu podobnego do @State, ale dla typów referencyjnych: SwiftUI tworzy boxingową otoczkę wokół obiektu i zarządza jej cyklem życia przez własny alokator, zoptymalizowany do częstych przebudów hierarchii View.
Główna różnica między @StateObject a @ObservedObject polega na tym, kto jest właścicielem obiektu. @StateObject tworzy i przechowuje obiekt — jest właścicielem. @ObservedObject tylko obserwuje obiekt, który został utworzony gdzie indziej i przekazany przez inicjalizator lub właściwość.
| Cecha | @StateObject | @ObservedObject |
|---|---|---|
| Posiadanie | Tworzy i jest właścicielem obiektu | Tylko obserwuje |
| Inicjalizacja | Wewnątrz View przez init/default | Z zewnątrz, przekazywany przez parametr |
| Cykl życia | Powiązany z cyklem życia View | Nie kontrolowany przez View |
| Odtworzenie | Nie jest odtwarzany przy aktualizacji | Może być zastąpiony z zewnątrz |
| Wersja iOS | iOS 14+ | iOS 13+ |
Zasada jest prosta: jeśli View tworzy ObservableObject — użyj @StateObject. Jeśli View tylko otrzymuje już gotowy obiekt od rodzica — użyj @ObservedObject. Naruszenie tej zasady prowadzi albo do utraty danych (jeśli używasz @ObservedObject do posiadania), albo do nadmiernego tworzenia obiektów (jeśli używasz @StateObject do obserwacji).
@StateObject należy używać w tych View, które są źródłem prawdy dla określonego zestawu danych. Typowe scenariusze obejmują ekrany z własnym view model, ekrany główne stosów nawigacyjnych oraz modalne prezentacje zarządzające własnym stanem.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Zapisz") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Inicjalizacja @StateObject z parametrami wymaga użycia specjalnej składni, ponieważ SwiftUI samodzielnie zarządza tworzeniem obiektu. Nie można po prostu przekazać parametrów do inicjalizatora — trzeba użyć escaping closure lub osobnej metody tworzenia.
Według Swift by Sundell (2024), najczystszym sposobem jest użycie metody fabrycznej lub closure, którą SwiftUI wywoła przy pierwszym utworzeniu obiektu. Alternatywne podejście — zainicjalizować ObservableObject w nadrzędnym View i przekazać go przez @StateObject z użyciem standardowego inicjalizatora.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
Ważne jest, aby pamiętać, że inicjalizator View z @StateObject musi używać podkreślenia przed nazwą właściwości (_viewModel), aby uzyskać dostęp do samego property wrappera, a nie do jego wartości. Jest to standardowy wzorzec Swift do pracy z property wrapperami w inicjalizatorach.
Najczęstszym błędem jest użycie @ObservedObject zamiast @StateObject dla View, które powinno posiadać obiekt. W takim przypadku przy każdej przebudowie rodzica obiekt będzie tworzony od nowa, co doprowadzi do utraty wszystkich zgromadzonych danych. Ten błąd jest szczególnie podstępny w złożonych hierarchiach z NavigationStack lub TabView.
Aby uniknąć tych problemów, przestrzegaj prostej zasady: jeden @StateObject na jedno źródło prawdy. Jeśli dane mają być współdzielone między wieloma ekranami — utwórz @StateObject raz w głównym View i przekazuj przez @ObservedObject lub @EnvironmentObject do podrzędnych elementów.
// ❌ Źle: @ObservedObject do posiadania obiektu
struct BadView: View {
@ObservedObject var vm = ViewModel() // zostanie odtworzony przy każdej aktualizacji!
}
// ✅ Dobrze: @StateObject do posiadania
struct GoodView: View {
@StateObject var vm = ViewModel() // utworzony raz na czas życia View
}
Często zadawane pytania
@State działa z typami wartościowymi (struktury, ciągi znaków, liczby) i przechowuje wartość bezpośrednio w magazynie SwiftUI. @StateObject działa z typami referencyjnymi — klasami zgodnymi z ObservableObject. @State nadaje się do prostych stanów lokalnych, @StateObject — do złożonych obiektów z logiką i opublikowanymi właściwościami.
Nie, @StateObject jest dostępny tylko od iOS 14 wzwyż. Dla iOS 13 używaj @ObservedObject i twórz ObservableObject w nadrzędnym View przez @State z ręcznym zarządzaniem cyklem życia. Alternatywą jest użycie @State z struct zamiast class dla danych, które nie wymagają semantyki referencyjnej.
Podrzędne View utworzy własną kopię ObservableObject, całkowicie niezależną od nadrzędnej. Zmiany w jednej nie odzwierciedlą się w drugiej. To prawie zawsze błąd: używaj @ObservedObject do otrzymywania obiektu od rodzica, a @StateObject tylko do tworzenia nowego obiektu wewnątrz View.
Obiekt jest niszczony, gdy View, które go utworzyło, zostaje całkowicie usunięte z hierarchii SwiftUI. Dla ekranu w NavigationStack następuje to przy pop ze stosu nawigacyjnego. Dla okna modalnego — przy jego zamknięciu. Dla TabView — przy przełączeniu karty, jeśli View nie jest buforowane.
Użyj niestandardowego init z dostępem do property wrapper przez podkreślenie: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Ten wzorzec pozwala przekazać dowolne parametry do ObservableObject, zachowując przy tym gwarancję jednokrotnego utworzenia obiektu w ciągu życia View.
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ż