@StateObject: co to jest, tworzenie i zarządzanie ObservableObject

Autor: IT Sectr Opublikowano: 2026-06-26 Czas czytania: 9 min

@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 — property wrapper do tworzenia i posiadania ObservableObject wewnątrz View.
  • Jednokrotne utworzenie — obiekt jest inicjalizowany raz w ciągu życia View i nie jest odtwarzany przy przebudowach.
  • Source of truth — @StateObject jest źródłem prawdy w hierarchii, w przeciwieństwie do @ObservedObject.
  • Cykl życia — obiekt żyje, dopóki View istnieje w pamięci, i jest niszczony wraz z nim.
  • Inicjalizacja — @StateObject wymaga wartości początkowej przy tworzeniu, zwykle przez init z parametrami.

Co to jest @StateObject w SwiftUI

@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.

swift
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)
        }
    }
}

Jak działa @StateObject

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.

Cykl życia @StateObject

  • Utworzenie — przy pierwszym pojawieniu się View na ekranie SwiftUI wywołuje inicjalizator obiektu i zapisuje referencję.
  • Przebudowa — przy aktualizacji nadrzędnego View obiekt nie jest odtwarzany, używana jest istniejąca instancja.
  • Zniszczenie — gdy View opuszcza ekran i jest usuwane z hierarchii, SwiftUI wywołuje deinit obiektu.

@StateObject vs @ObservedObject: kluczowe różnice

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
PosiadanieTworzy i jest właścicielem obiektuTylko obserwuje
InicjalizacjaWewnątrz View przez init/defaultZ zewnątrz, przekazywany przez parametr
Cykl życiaPowiązany z cyklem życia ViewNie kontrolowany przez View
OdtworzenieNie jest odtwarzany przy aktualizacjiMoże być zastąpiony z zewnątrz
Wersja iOSiOS 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).

Kiedy używać @StateObject

@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.

  • Ekran z view model — każdy ekran, który zarządza własnymi danymi i logiką, powinien tworzyć swój view model przez @StateObject.
  • Główny View — w hierarchii NavigationStack lub TabView główny element tworzy dane, a podrzędne otrzymują je przez @ObservedObject.
  • Okna modalne — .sheet i .fullScreenCover często wymagają własnego @StateObject do zarządzania formularzem lub procesem.
  • Lista z edycją — każdy wiersz listy zawierający formularz edycji powinien mieć własny @StateObject.
swift
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

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.

swift
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.

Typowe błędy z @StateObject

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.

  • Utrata danych przy nawigacji — jeśli podrzędny ekran używa @ObservedObject dla własnego view model, przy powrocie i ponownym otwarciu dane zostaną zresetowane.
  • Wyciek pamięci — tworzenie @StateObject w nadrzędnym View, które nigdy nie jest usuwane, może prowadzić do gromadzenia obiektów, jeśli każdy podrzędny ekran również tworzy @StateObject bez kontroli.
  • Duplikowanie obiektów — przekazanie jednego ObservableObject do wielu @StateObject w różnych View tworzy kilka niezależnych instancji, które nie synchronizują się między sobą.

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.

swift
// ❌ Ź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

Jaka jest różnica między @StateObject a @State?

@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.

Czy można używać @StateObject w iOS 13?

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.

Co się stanie, jeśli użyję @StateObject w podrzędnym View, do którego obiekt jest przekazywany z rodzica?

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.

Kiedy niszczony jest obiekt utworzony przez @StateObject?

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.

Jak przekazać parametry do @StateObject przy inicjalizacji?

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

  • @StateObject — property wrapper do tworzenia i posiadania ObservableObject wewnątrz View, dostępny od iOS 14.
  • Gwarancja jednokrotnego utworzenia — obiekt jest inicjalizowany raz i nie jest odtwarzany przy przebudowie View.
  • Source of truth — @StateObject jest źródłem prawdy, a @ObservedObject tylko obserwatorem.
  • Cykl życia — obiekt żyje, dopóki View istnieje w hierarchii SwiftUI, i jest niszczony przy jej opuszczeniu.
  • Inicjalizacja z parametrami — wymaga dostępu do property wrapper przez _viewModel i StateObject(wrappedValue:).
  • Błąd posiadania — użycie @ObservedObject do tworzenia obiektu prowadzi do utraty danych przy przebudowie.
  • Jeden obiekt — jeden @StateObject — dla współdzielonych danych twórz @StateObject w głównym View i przekazuj podrzędnym przez @ObservedObject.

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.

Omów projekt

Przeczytaj również