Az @StateObject egy Property Wrapper a SwiftUI-ban ObservableObject példány létrehozására és birtoklására közvetlenül a view-ban. A SwiftUI garantálja, hogy az objektum egyszer inicializálódik a nézet életciklusa során, és nem jön létre újra ismételt renderelések során. A Apple Developer Documentation (2025) szerint az @StateObject azokhoz a gyökér nézetekhez ajánlott, amelyek adatforrást hoznak létre. @StateObject a helyes választás az ObservableObject birtoklásához a SwiftUI hierarchiában.
Főbb pontok
@StateObject egy Property Wrapper, amely a SwiftUI 2.0-ban (iOS 14) jelent meg, és egyesíti az @ObservedObject és @State képességeit. Az @ObservedObject-hez hasonlóan feliratkozik az ObservableObject változásaira. Az @State-hez hasonlóan garantálja, hogy az adatok túlélik a view struktúra többszöri inicializálását. Az @StateObject egyszer hozza létre az objektumot a view első képernyőn való megjelenésekor, és a SwiftUI heap-ben tárolja.
Az @StateObject megjelenése előtt a fejlesztők az @ObservedObject-et használták minden ObservableObject-hez, beleértve a view-kban létrehozottakat is. Ez gyakori adatvesztéshez vezetett a szülő view frissítésekor, amikor a view struktúra újra létrejött, magával vive az @ObservedObject példányt is. Az @StateObject megoldotta ezt a problémát a stabilitás garanciájának hozzáadásával.
A fő szabály: az @StateObject abban a view-ban alkalmazandó, amely létrehozza az objektumot az alapértelmezett inicializátorban (let model = ViewModel()). A gyermek nézetek, amelyek ezt az objektumot kapják, @ObservedObject-et használnak. Ez a felosztás egyetlen igazságforrást garantál a teljes hierarchiában.
A SwiftUI az @StateObject életciklusát egy tárolókezelőn keresztül irányítja, hasonlóan az @State-hez. A view első megjelenésekor a SwiftUI memóriát foglal az objektum számára, és elmenti egy perzisztens területre. Ismételt renderelések (body hívás) során az objektum nem jön létre újra — a meglévő példány használatos. Az objektum addig él, amíg a view a hierarchiában van.
Amikor a view eltávolításra kerül a hierarchiából, a SwiftUI megsemmisíti az @StateObject-et, meghívva a deinit-et. A view hierarchiába való visszahelyezésekor új példány jön létre. Ezt fontos figyelembe venni a tervezésnél: ha meg kell őrizni az adatokat a view eltávolítások között, használjon szolgáltatási réteget (singleton vagy DI) vagy @AppStorage-t a perzisztenciához.
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() }
}
}
A példában a TimerViewModel az @StateObject-en keresztül jön létre, és addig él, amíg a TimerView a képernyőn van. A Timer az onAppear-ben indul és a deinit-ben áll le. Ha @ObservedObject-et használtak volna, minden TimerView rendereléskor új TimerViewModel jönne létre másodpercek = 0 értékkel, és a timer soha nem működne helyesen. Az @StateObject garantálja, hogy a viewModel egyedi és stabil.
A választás az @StateObject és az @ObservedObject között attól függ, ki birtokolja az objektumot. Ha a view hozza létre az objektumot — @StateObject. Ha a view kész objektumot kap — @ObservedObject. Ez a szabály annyira fontos, hogy az Xcode figyelmeztetést ad az @StateObject használatakor egy gyermek view-ban, amely inicializátoron keresztül kapja az objektumot.
| Helyzet | Ajánlott wrapper |
|---|---|
| View létrehozza a modellt ViewModel() segítségével | @StateObject |
| View megkapja a modellt a szülőtől | @ObservedObject |
| Modell egy view-ban használatos | @StateObject |
| Modell Environment-en keresztül kerül átadásra | @EnvironmentObject |
| Modell előnézethez szükséges | @ObservedObject + mock |
A gyakorlatban a projekt elején gyakran használnak @StateObject-et a gyökér view-ban és @ObservedObject-et az összes gyermek view-ban. Ahogy az alkalmazás nő, az @StateObject egy része helyettesíthető @EnvironmentObject-tel a hierarchia egyszerűsítése érdekében. Az @StateObject azonban továbbra is a legjobb választás a saját logikával rendelkező moduláris képernyőkhöz.
Az első minta — MVVM @StateObject-tel. A ViewModel mint ObservableObject a view-ban jön létre az @StateObject-en keresztül. A ViewModel @Published tulajdonságokat és üzleti logikát tartalmaz. A view feliratkozik a változásokra és frissíti a felületet. Ez a megközelítés tesztelhető izolációt biztosít: a ViewModel UI nélkül tesztelhető a példány közvetlen létrehozásával.
A második minta — @StateObject függőségekkel. Ha a ViewModel szolgáltatásokat igényel, használjon paraméteres inicializálást. Például @StateObject var viewModel = UserViewModel(api: APIClient.shared). Azonban legyen óvatos: a paraméterek minden body rendereléskor kiszámításra kerülnek, de az objektum csak egyszer jön létre. A SwiftUI figyelmen kívül hagyja az @StateObject későbbi inicializálásait.
A harmadik minta — egymásba ágyazott @StateObject. A SwiftUI-ban több @StateObject is lehet egy view-ban, de ez ritkán indokolt. Általában egy @StateObject felel a view teljes adatkészletéért. Ha a logika túl bonyolulttá válik, ossza fel @ObservedObject szolgáltatások kompozíciójára egy @StateObject-en belül.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
A példában az AppView két @StateObject-et hoz létre: NavigationRouter-t a navigáció kezeléséhez és AuthViewModel-et a hitelesítéshez. Mindkét objektum az environmentObject-en keresztül kerül az Environment-be. Bármely gyermek view hozzáférhet hozzájuk az @EnvironmentObject-en keresztül anélkül, hogy át kellene adni őket az inicializátorok láncán.
@StateObject támogatja az inicializálást bármilyen paraméterrel, de egy fontos jellemzővel: az inicializátor csak egyszer hívódik meg. A body ismételt renderelésekor a paraméterek új értéke figyelmen kívül marad. Ez azt jelenti, hogy ha @State var id: Int = 5-öt ad át az @StateObject var vm = ViewModel(id: id)-nek, az id változásakor a ViewModel nem kap új értéket.
A probléma megoldásához használjon onReceive vagy onAppear szinkronizáláshoz. Iratkozzon fel a paraméter változásaira a ViewModel-en belül Combine-on keresztül, vagy adja át a paramétereket a .onChange(of:) metódussal a view szintjén. Alternatíva — használjon @ObservedObject-et az @StateObject helyett, ha az objektumnak dinamikusan kell reagálnia a külső változásokra.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
A helyes megközelítés: a DetailView let tulajdonságként kapja az itemId-t (a struktúra inicializátorán keresztül), az @StateObject pedig paraméterek nélkül hozza létre a DetailViewModel-et. Az onAppear-ben meghívódik a load(id:) metódus, amely betölti az adatokat a megadott ID-hoz. Ez garantálja, hogy a ViewModel az @StateObject mechanizmusával jött létre, de az adatok minden view megjelenésekor az aktuális ID-val töltődnek be.
A fő hiba — az @StateObject használata gyermek view-okban, amelyek a szülőtől kapják az objektumot. Ha a ParentView létrehozza az @StateObject model-t, és a ChildView @StateObject var model: ModelType-t deklarál (alapértelmezett paraméterrel), akkor a ChildView létrehozza a saját független példányát. A szülő és gyermek objektumai nem lesznek összekapcsolva, és a változások az egyikben nem tükröződnek a másikban.
A második hiba — @StateObject elhelyezése List vagy ForEach elemben. A lista minden eleme létrehozza a saját @StateObject-jét, ami több független példányhoz vezet. Listák esetében helyes egy ObservableObject-et átadni minden elemnek @ObservedObject-en keresztül, vagy Identifiable struktúrákat használni @State-vel a Listán belül.
A harmadik probléma — a deinit tisztítás hiánya. Az @StateObject a view teljes életciklusa alatt él. Ha az objektum időzítőket, Combine feliratkozásokat vagy hálózati kéréseket hoz létre, a deinit-nek le kell őket mondania. Ellenkező esetben memóriaszivárgás és a háttérmunka folytatása a képernyő bezárása után elkerülhetetlen. Mindig használjon Combine Cancellable store-t vagy időzítők invalidate-ét a deinit-ben.
Gyakran ismételt kérdések
@StateObject a SwiftUI 2.0-ban került hozzáadásra a WWDC 2020 során az iOS 14, macOS 11, watchOS 7 és tvOS 14 mellett. Ezelőtt az @ObservedObject volt az egyetlen mód az ObservableObject-tel való munkára, ami gyakori adatvesztési hibákhoz vezetett.
Nem, az @StateObject nem támogatja az Optional típusokat. Az objektumot a deklaráláskor inicializálni kell. Ha opcionális objektumra van szüksége, használjon @ObservedObject vagy @EnvironmentObject opcionális típussal.
Adjon hozzá print(#function) az ObservableObject inicializátorához és deinit-jéhez. Ha az init nem hívódik meg ismételt renderelések során — az @StateObject helyesen működik. Ha az init minden alkalommal meghívódik — cserélje ki az @ObservedObject-et @StateObject-re.
Igen, az @StateObject működik a UIHostingController-en keresztül UIKit-be ágyazott SwiftUI view-okban. Az objektum életciklusa a SwiftUI view-hoz kötődik, nem a UIViewController-hez. Ha a SwiftUI view lecserélődik, az @StateObject megsemmisül.
Több kicsi @StateObject elkülönített felelősséggel. Ez javítja a tesztelhetőséget, újrafelhasználhatóságot és teljesítményt — egy objektum változásakor csak a feliratkozott felületi részek rajzolódnak újra, nem a teljes view.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is