@StateObject je Property Wrapper ve SwiftUI pro vytvoření a vlastnictví instance ObservableObject přímo ve view. SwiftUI zaručuje, že objekt je inicializován jednou za životní cyklus zobrazení a není znovu vytvářen při opakovaných renderech. Podle Apple Developer Documentation (2025) je @StateObject doporučen pro kořenová view vytvářející zdroj dat. @StateObject je správná volba pro vlastnictví ObservableObject v hierarchii SwiftUI.
Hlavní body
@StateObject je Property Wrapper, který se objevil ve SwiftUI 2.0 (iOS 14) a kombinuje možnosti @ObservedObject a @State. Stejně jako @ObservedObject se přihlašuje ke změnám ObservableObject. Stejně jako @State zaručuje, že data přežijí vícenásobné inicializace struktury view. @StateObject vytváří objekt jednou při prvním zobrazení view na obrazovce a ukládá ho na haldě SwiftUI.
Před příchodem @StateObject používali vývojáři @ObservedObject pro všechny ObservableObject, včetně těch vytvořených ve view. To vedlo k časté ztrátě dat při aktualizaci nadřazeného view, kdy byla struktura view znovu vytvořena a vzala s sebou i instanci @ObservedObject. @StateObject vyřešil tento problém přidáním záruky stability.
Základní pravidlo: @StateObject se aplikuje ve view, které vytváří objekt ve výchozím inicializátoru (let model = ViewModel()). Dceřiná view, která tento objekt přijímají, používají @ObservedObject. Takové rozdělení zaručuje jediný zdroj pravdy v celé hierarchii.
SwiftUI spravuje životní cyklus @StateObject prostřednictvím správce úložiště, analogicky k @State. Při prvním zobrazení view SwiftUI alokuje paměť pro objekt a uloží ho do persistentní oblasti. Při opakovaných renderech (volání body) není objekt znovu vytvářen — používá se existující instance. Objekt žije, dokud je view v hierarchii.
Když je view odstraněno z hierarchie, SwiftUI zničí @StateObject a zavolá deinit. Při opětovném přidání view do hierarchie je vytvořena nová instance. To je důležité vzít v úvahu při návrhu: pokud potřebujete uchovat data mezi odstraněními view, použijte servisní vrstvu (singleton nebo DI) nebo @AppStorage pro perzistenci.
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() }
}
}
V příkladu je TimerViewModel vytvořen přes @StateObject a žije, dokud je TimerView na obrazovce. Timer se spouští v onAppear a zastavuje v deinit. Pokud by byl použit @ObservedObject, při každém renderu TimerView by byl vytvořen nový TimerViewModel s sekundami = 0 a timer by nikdy nepracoval správně. @StateObject zaručuje, že viewModel je jedinečný a stabilní.
Výběr mezi @StateObject a @ObservedObject závisí na tom, kdo vlastní objekt. Pokud view vytváří objekt — @StateObject. Pokud view přijímá hotový objekt — @ObservedObject. Toto pravidlo je tak důležité, že Xcode vydává varování při použití @StateObject v dceřiném view, které přijímá objekt přes inicializátor.
| Situace | Doporučený wrapper |
|---|---|
| View vytváří model přes ViewModel() | @StateObject |
| View přijímá model od rodiče | @ObservedObject |
| Model je použit v jednom view | @StateObject |
| Model je předáván přes Environment | @EnvironmentObject |
| Model je potřeba pro náhled | @ObservedObject + mock |
V praxi se na začátku projektu často používá @StateObject v kořenovém view a @ObservedObject ve všech dceřiných view. Jak aplikace roste, část @StateObject může být nahrazena @EnvironmentObject pro zjednodušení hierarchie. @StateObject však zůstává nejlepší volbou pro modulární obrazovky s vlastní logikou.
První vzor — MVVM s @StateObject. ViewModel jako ObservableObject je vytvořen ve view přes @StateObject. ViewModel obsahuje @Published vlastnosti a obchodní logiku. View se přihlašuje ke změnám a aktualizuje rozhraní. Tento přístup poskytuje testovatelnou izolaci: ViewModel lze testovat bez UI vytvořením instance přímo.
Druhý vzor — @StateObject se závislostmi. Pokud ViewModel vyžaduje služby, použijte inicializaci s parametry. Například @StateObject var viewModel = UserViewModel(api: APIClient.shared). Buďte však opatrní: parametry se vypočítávají při každém renderu body, ale objekt je vytvořen pouze jednou. SwiftUI ignoruje následné inicializace @StateObject.
Třetí vzor — vnořené @StateObject. Ve SwiftUI můžete mít více @StateObject v jednom view, ale je to zřídka oprávněné. Obvykle jeden @StateObject odpovídá za celou sadu dat view. Pokud je logika příliš složitá, rozdělte ji na kompozici @ObservedObject služeb v rámci jednoho @StateObject.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
V příkladu AppView vytváří dva @StateObject: NavigationRouter pro správu navigace a AuthViewModel pro autentizaci. Oba objekty jsou injektovány do Environment přes environmentObject. Libovolné dceřiné view k nim může přistupovat přes @EnvironmentObject bez předávání přes řetězec inicializátorů.
@StateObject podporuje inicializaci s libovolnými parametry, ale s důležitou vlastností: inicializátor je volán pouze jednou. Při opakovaných renderech body je nová hodnota parametrů ignorována. To znamená, že pokud předáte @State var id: Int = 5 do @StateObject var vm = ViewModel(id: id), při změně id ViewModel neobdrží novou hodnotu.
Pro vyřešení tohoto problému použijte onReceive nebo onAppear pro synchronizaci. Přihlaste se ke změnám parametru uvnitř ViewModel přes Combine nebo předávejte parametry přes metodu .onChange(of:) na úrovni view. Alternativa — použijte @ObservedObject místo @StateObject, pokud má objekt dynamicky reagovat na vnější změny.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
Správný přístup: DetailView přijímá itemId jako let vlastnost (předanou přes inicializátor struktury) a @StateObject vytváří DetailViewModel bez parametrů. V onAppear je volána metoda load(id:), která načítá data pro předané ID. To zaručuje, že ViewModel je vytvořen mechanismem @StateObject, ale data jsou načítána při každém zobrazení view s aktuálním ID.
Hlavní chyba — použití @StateObject v dceřiných view, která přijímají objekt od rodiče. Pokud ParentView vytváří @StateObject model a ChildView deklaruje @StateObject var model: ModelType (s výchozím parametrem), ChildView vytvoří vlastní nezávislou instanci. Objekty rodiče a dítěte nebudou propojeny a změny v jednom se neprojeví v druhém.
Druhá chyba — umístění @StateObject v List nebo ForEach. Každý prvek seznamu vytváří svůj vlastní @StateObject, což vede k mnoha nezávislým instancím. Pro seznamy je správné předat jeden ObservableObject všem prvkům přes @ObservedObject nebo použít Identifiable struktury s @State uvnitř List.
Třetí problém — chybějící čištění v deinit. @StateObject žije po celý životní cyklus view. Pokud objekt vytváří časovače, Combine předplatná nebo síťové požadavky, deinit je musí zrušit. Jinak jsou nevyhnutelné úniky paměti a pokračování práce na pozadí po zavření obrazovky. Vždy používejte Combine Cancellable store nebo invalidate časovačů v deinit.
Často kladené otázky
@StateObject byl přidán ve SwiftUI 2.0 na WWDC 2020 spolu s iOS 14, macOS 11, watchOS 7 a tvOS 14. Předtím byl @ObservedObject jediným způsobem práce s ObservableObject, což vedlo k častým chybám se ztrátou dat.
Ne, @StateObject nepodporuje typy Optional. Objekt musí být inicializován při deklaraci. Pokud potřebujete volitelný objekt, použijte @ObservedObject nebo @EnvironmentObject s volitelným typem.
Přidejte print(#function) do inicializátoru a deinit ObservableObject. Pokud se init nevolá při opakovaných renderech — @StateObject funguje správně. Pokud se init volá pokaždé — nahraďte @ObservedObject za @StateObject.
Ano, @StateObject funguje ve SwiftUI view vložených do UIKit přes UIHostingController. Životní cyklus objektu je vázán na SwiftUI view, nikoli na UIViewController. Pokud je SwiftUI view nahrazeno, @StateObject je zničen.
Několik malých @StateObject s rozdělenou odpovědností. To zlepšuje testovatelnost, znovupoužitelnost a výkon — při změně jednoho objektu se překreslí pouze přihlášené části rozhraní, nikoli celé view.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také