@StateObject: co to jest, różnica od @ObservedObject i przykłady

Autor: IT Sectr Opublikowano: 2026-06-19 Czas czytania: 7 min

@StateObject to Property Wrapper w SwiftUI do tworzenia i posiadania instancji ObservableObject bezpośrednio w widoku. SwiftUI gwarantuje, że obiekt jest inicjalizowany raz na cykl życia widoku i nie jest odtwarzany przy ponownym renderowaniu. Według Apple Developer Documentation (2025), @StateObject jest zalecany dla widoków głównych tworzących źródło danych. @StateObject to właściwy wybór do posiadania ObservableObject w hierarchii SwiftUI.

Najważniejsze

  • @StateObject — Property Wrapper do tworzenia i posiadania ObservableObject w widoku
  • Pojedyncza instancja — obiekt tworzony jest raz i nie jest odtwarzany przy renderowaniach
  • Źródło prawdy — @StateObject gwarantuje stabilność danych dla całej hierarchii
  • Różnica od @ObservedObject — @ObservedObject nie posiada obiektu i może go utracić
  • Widoki główne — @StateObject jest używany w widoku, który tworzy obiekt

Co to jest @StateObject w SwiftUI?

@StateObject to Property Wrapper, który pojawił się w SwiftUI 2.0 (iOS 14), łączący możliwości @ObservedObject i @State. Podobnie jak @ObservedObject, subskrybuje zmiany ObservableObject. Podobnie jak @State, gwarantuje, że dane przetrwają wielokrotne inicjalizacje struktury widoku. @StateObject tworzy obiekt raz przy pierwszym pojawieniu się widoku na ekranie i przechowuje go w stercie SwiftUI.

Przed pojawieniem się @StateObject programiści używali @ObservedObject dla wszystkich ObservableObject, w tym tworzonych w widokach. Prowadziło to do częstej utraty danych podczas aktualizacji widoku nadrzędnego, gdy struktura widoku była odtwarzana, zabierając ze sobą instancję @ObservedObject. @StateObject rozwiązał ten problem, dodając gwarancję stabilności.

Główna zasada: @StateObject stosuje się w widoku, który tworzy obiekt w domyślnym inicjalizatorze (let model = ViewModel()). Widoki potomne, które otrzymują ten obiekt, używają @ObservedObject. Taki podział gwarantuje jedyne źródło prawdy w całej hierarchii.

Cykl życia @StateObject

SwiftUI zarządza cyklem życia @StateObject przez menedżer przechowywania, analogiczny do @State. Przy pierwszym pojawieniu się widoku SwiftUI przydziela pamięć dla obiektu i zapisuje go w obszarze trwałym. Przy ponownym renderowaniu (wywołanie body) obiekt nie jest odtwarzany — używana jest istniejąca instancja. Obiekt żyje, dopóki widok znajduje się w hierarchii.

Gdy widok jest usuwany z hierarchii, SwiftUI niszczy @StateObject, wywołując deinit. Przy ponownym dodaniu widoku do hierarchii tworzona jest nowa instancja. Należy to uwzględnić przy projektowaniu: jeśli trzeba zachować dane między usunięciami widoku, użyj warstwy serwisowej (singleton lub DI) lub @AppStorage dla trwałości.

swift
class TimerViewModel: ObservableObject {
    @Published var seconds: Int = 0
    private var timer: Timer?

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.seconds += 1
        }
    }

    deinit {
        timer?.invalidate()
    }
}

struct TimerView: View {
    @StateObject var viewModel = TimerViewModel()

    var body: some View {
        Text("\(viewModel.seconds)s")
            .onAppear { viewModel.start() }
    }
}

W przykładzie TimerViewModel jest tworzony przez @StateObject i żyje, dopóki TimerView jest na ekranie. Timer uruchamia się w onAppear i zatrzymuje w deinit. Gdyby użyto @ObservedObject, przy każdym renderowaniu TimerView tworzony byłby nowy TimerViewModel z sekundami = 0, a timer nigdy nie działałby poprawnie. @StateObject gwarantuje, że viewModel jest jedyny i stabilny.

@StateObject vs @ObservedObject: porównanie

Wybór między @StateObject a @ObservedObject zależy od tego, kto posiada obiekt. Jeśli widok tworzy obiekt — @StateObject. Jeśli widok otrzymuje gotowy obiekt — @ObservedObject. Ta zasada jest tak ważna, że Xcode wyświetla ostrzeżenie przy użyciu @StateObject w widoku potomnym, który otrzymuje obiekt przez inicjalizator.

SytuacjaZalecany wrapper
Widok tworzy model przez ViewModel()@StateObject
Widok otrzymuje model od rodzica@ObservedObject
Model jest używany w jednym widoku@StateObject
Model jest przekazywany przez Environment@EnvironmentObject
Model jest potrzebny do podglądu@ObservedObject + mock

W praktyce na początku projektu często używa się @StateObject w widoku głównym i @ObservedObject we wszystkich potomnych. W miarę wzrostu aplikacji część @StateObject może być zastąpiona przez @EnvironmentObject w celu uproszczenia hierarchii. Jednak @StateObject pozostaje najlepszym wyborem dla modułowych ekranów z własną logiką.

Wzorce użycia @StateObject

Pierwszy wzorzec — MVVM z @StateObject. ViewModel jako ObservableObject jest tworzony w widoku przez @StateObject. ViewModel zawiera właściwości @Published i logikę biznesową. Widok subskrybuje zmiany i aktualizuje interfejs. Takie podejście zapewnia testowalną izolację: ViewModel można testować bez UI, tworząc instancję bezpośrednio.

Drugi wzorzec — @StateObject z zależnościami. Jeśli ViewModel wymaga serwisów, użyj inicjalizacji z parametrami. Na przykład @StateObject var viewModel = UserViewModel(api: APIClient.shared). Należy jednak uważać: parametry są obliczane przy każdym renderowaniu body, ale obiekt jest tworzony tylko raz. SwiftUI ignoruje kolejne inicjalizacje @StateObject.

Trzeci wzorzec — zagnieżdżone @StateObject. W SwiftUI można mieć wiele @StateObject w jednym widoku, ale rzadko jest to uzasadnione. Zazwyczaj jeden @StateObject odpowiada za cały zestaw danych widoku. Jeśli logika staje się zbyt skomplikowana, podziel ją na kompozycję @ObservedObject serwisów wewnątrz jednego @StateObject.

swift
struct AppView: View {
    @StateObject var router = NavigationRouter()
    @StateObject var auth = AuthViewModel()

    var body: some View {
        ContentView()
            .environmentObject(router)
            .environmentObject(auth)
    }
}

W przykładzie AppView tworzy dwa @StateObject: NavigationRouter do zarządzania nawigacją i AuthViewModel do uwierzytelniania. Oba obiekty są wstrzykiwane do Environment przez environmentObject. Każdy widok potomny może uzyskać do nich dostęp przez @EnvironmentObject bez przekazywania przez łańcuch inicjalizatorów.

@StateObject i inicjalizacja z parametrami

@StateObject obsługuje inicjalizację z dowolnymi parametrami, ale z ważną cechą: inicjalizator jest wywoływany tylko raz. Przy ponownym renderowaniu body nowa wartość parametrów jest ignorowana. Oznacza to, że jeśli przekażesz @State var id: Int = 5 do @StateObject var vm = ViewModel(id: id), to przy zmianie id ViewModel nie otrzyma nowej wartości.

Aby rozwiązać ten problem, użyj onReceive lub onAppear do synchronizacji. Subskrybuj zmiany parametru wewnątrz ViewModel przez Combine lub przekazuj parametry przez metodę .onChange(of:) na poziomie widoku. Alternatywą jest użycie @ObservedObject zamiast @StateObject, jeśli obiekt ma dynamicznie reagować na zmiany zewnętrzne.

swift
struct DetailView: View {
    let itemId: Int
    @StateObject var viewModel = DetailViewModel()

    var body: some View {
        Text(viewModel.title)
            .onAppear { viewModel.load(id: itemId) }
    }
}

Prawidłowe podejście: DetailView otrzymuje itemId jako właściwość let (przekazywaną przez inicjalizator struktury), a @StateObject tworzy DetailViewModel bez parametrów. W onAppear wywoływana jest metoda load(id:), która ładuje dane dla przekazanego ID. Gwarantuje to, że ViewModel jest tworzony przez mechanizm @StateObject, ale dane są ładowane przy każdym pojawieniu się widoku z aktualnym ID.

Typowe błędy z @StateObject

Główny błąd — używanie @StateObject w widokach potomnych, które otrzymują obiekt od rodzica. Jeśli ParentView tworzy @StateObject model, a ChildView deklaruje @StateObject var model: ModelType (z parametrem domyślnym), to ChildView utworzy własną niezależną instancję. Obiekty rodzica i dziecka nie będą powiązane, a zmiany w jednym nie odzwierciedlą się w drugim.

Drugi błąd — umieszczanie @StateObject w List lub ForEach. Każdy element listy tworzy swój własny @StateObject, co prowadzi do wielu niezależnych instancji. Dla list poprawnie jest przekazać jeden ObservableObject wszystkim elementom przez @ObservedObject lub użyć struktur Identifiable z @State wewnątrz List.

Trzeci problem — brak czyszczenia w deinit. @StateObject żyje przez cały cykl życia widoku. Jeśli obiekt tworzy timery, subskrypcje Combine lub zapytania sieciowe, deinit powinien je anulować. W przeciwnym razie wycieki pamięci i kontynuacja pracy w tle po zamknięciu ekranu są nieuniknione. Zawsze używaj Cancellable store Combine lub invalidate timerów w deinit.

Często zadawane pytania

Kiedy pojawił się @StateObject w SwiftUI?

@StateObject został dodany w SwiftUI 2.0 na WWDC 2020 wraz z iOS 14, macOS 11, watchOS 7 i tvOS 14. Wcześniej @ObservedObject był jedynym sposobem pracy z ObservableObject, co prowadziło do częstych błędów z utratą danych.

Czy @StateObject może być opcjonalny?

Nie, @StateObject nie obsługuje typów Optional. Obiekt musi być zainicjalizowany przy deklaracji. Jeśli potrzebujesz opcjonalnego obiektu, użyj @ObservedObject lub @EnvironmentObject z typem opcjonalnym.

Jak sprawdzić, że @StateObject został utworzony tylko raz?

Dodaj print(#function) w inicjalizatorze i deinit ObservableObject. Jeśli przy ponownym renderowaniu init nie jest wywoływany — @StateObject działa poprawnie. Jeśli init jest wywoływany za każdym razem — zastąp @ObservedObject na @StateObject.

Czy można używać @StateObject z UIKit przez UIHostingController?

Tak, @StateObject działa w widokach SwiftUI osadzonych w UIKit przez UIHostingController. Cykl życia obiektu jest powiązany z widokiem SwiftUI, a nie z UIViewController. Jeśli widok SwiftUI jest zastępowany, @StateObject jest niszczony.

Co jest lepsze: jeden @StateObject z dużym ViewModel czy kilka małych?

Kilka małych @StateObject z podzieloną odpowiedzialnością. Poprawia to testowalność, współużytkowanie i wydajność — przy zmianie jednego obiektu przerysowywane są tylko subskrybujące części interfejsu, a nie cały widok.

Podsumowanie

  • @StateObject — Property Wrapper do tworzenia i posiadania ObservableObject w widoku
  • Pojedyncza instancja — obiekt nie jest odtwarzany przy ponownym renderowaniu body
  • Źródło prawdy — @StateObject w widoku głównym gwarantuje stabilność danych dla hierarchii
  • Zasada wyboru — @StateObject do tworzenia, @ObservedObject do otrzymywania gotowego obiektu
  • Inicjalizacja — parametry w @StateObject są obliczane raz, aktualizacje nie są śledzone
  • Deinit — obowiązkowe czyszczenie timerów i subskrypcji w deinit ObservableObject
  • iOS 14+ — @StateObject dostępny od iOS 14, macOS 11, watchOS 7, tvOS 14

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ż