@StateObject este un property wrapper în SwiftUI care creează și deține o instanță ObservableObject pe tot parcursul ciclului de viață al View. Când View apare pentru prima dată pe ecran, @StateObject inițializează obiectul și îl păstrează până când View este eliminat din memorie. Acest lucru garantează că datele nu se resetează la reconstruirea interfeței — de exemplu, la schimbarea temei sau actualizarea View-ului părinte. Conform Apple Developer Documentation (2025), @StateObject ar trebui folosit ca sursă principală de adevăr (source of truth) pentru ObservableObject în ierarhia SwiftUI, în timp ce View-urile copil primesc obiectul deja creat prin @ObservedObject sau @EnvironmentObject.
Principalele puncte
@StateObject este un property wrapper introdus în iOS 14 care permite unui View să creeze și să dețină o instanță a unei clase conforme cu protocolul ObservableObject. Spre deosebire de @State, care lucrează cu tipuri valoare (structuri), @StateObject este destinat tipurilor referință — clase care pot notifica SwiftUI despre modificările proprietăților lor.
Când View folosește @StateObject var viewModel: MyViewModel, SwiftUI creează automat instanța MyViewModel la prima afișare a View și o stochează într-un depozit special al framework-ului. La fiecare actualizare a View (de exemplu, la modificarea stării părinte), SwiftUI nu recrează obiectul — folosește instanța existentă până când View este eliminat din ierarhie.
Conform Apple WWDC Session 10137 (2024), @StateObject rezolvă problema pierderii de date la reconstruirea View care exista în iOS 13, când dezvoltatorii trebuiau să creeze ObservableObject în View-ul părinte și să îl transmită prin inițializator. Aceasta ducea la duplicarea codului și riscul de recreere accidentală a obiectului.
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("Număr: \(viewModel.count)")
Button("Incrementează", action: viewModel.increment)
}
}
}
Mecanismul @StateObject se bazează pe integrarea SwiftUI cu framework-ul Combine. Când ObservableObject își marchează proprietățile cu atributul @Published, SwiftUI se abonează automat la modificări prin publisher-ul încorporat în protocolul ObservableObject. La modificarea unei proprietăți publicate, obiectul trimite un semnal prin publisher-ul objectWillChange, ceea ce declanșează redesenarea tuturor View-urilor care observă acest obiect.
SwiftUI stochează instanța ObservableObject într-un depozit special legat de o anumită instanță View. Acest depozit este creat o dată la primul randament și există până la distrugerea View. Tocmai de aceea @StateObject garantează stabilitatea referinței la obiect — SwiftUI gestionează memoria automat, fără a se baza pe inițializatorul View.
Conform objc.io — Thinking in SwiftUI (2025), implementarea internă a @StateObject folosește un mecanism similar cu @State, dar pentru tipuri referință: SwiftUI creează o împachetare boxing în jurul obiectului și gestionează ciclul său de viață prin propriul alocator, optimizat pentru reconstruiri frecvente ale ierarhiei View.
Diferența principală între @StateObject și @ObservedObject constă în cine deține obiectul. @StateObject creează și stochează obiectul — el este proprietarul. @ObservedObject doar observă un obiect care a fost creat în altă parte și transmis prin inițializator sau proprietate.
| Caracteristică | @StateObject | @ObservedObject |
|---|---|---|
| Deținere | Creează și deține obiectul | Doar observă |
| Inițializare | În interiorul View prin init/default | Din exterior, transmis prin parametru |
| Ciclul de viață | Legat de ciclul de viață al View | Nu este controlat de View |
| Recreere | Nu este recreat la actualizare | Poate fi înlocuit din exterior |
| Versiune iOS | iOS 14+ | iOS 13+ |
Regula este simplă: dacă View creează ObservableObject — folosește @StateObject. Dacă View doar primește un obiect deja gata de la părinte — folosește @ObservedObject. Încălcarea acestei reguli duce fie la pierderea datelor (dacă folosești @ObservedObject pentru deținere), fie la crearea excesivă de obiecte (dacă folosești @StateObject pentru observare).
@StateObject trebuie folosit în acele View-uri care sunt sursa de adevăr pentru un set specific de date. Scenariile tipice includ ecrane cu propriul view model, ecranele rădăcină ale stivelor de navigare și prezentările modale care își gestionează propria stare.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Salvează") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Inițializarea @StateObject cu parametri necesită o sintaxă specială, deoarece SwiftUI gestionează crearea obiectului în mod independent. Nu poți pur și simplu transmite parametri în inițializator — trebuie să folosești o închidere escaping sau o metodă separată de creare.
Conform Swift by Sundell (2024), cea mai curată metodă este utilizarea unei metode fabrică sau a unei închideri pe care SwiftUI o va apela la prima creare a obiectului. O abordare alternativă — inițializarea ObservableObject în View-ul părinte și transmiterea lui prin @StateObject cu ajutorul inițializatorului standard.
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)
}
}
Este important de reținut că inițializatorul View cu @StateObject trebuie să folosească underscore înaintea numelui proprietății (_viewModel) pentru a accesa property wrapper-ul în sine, nu valoarea acestuia. Acesta este un model standard Swift pentru lucrul cu property wrapper-e în inițializatori.
Cea mai frecventă greșeală este utilizarea @ObservedObject în loc de @StateObject pentru View care ar trebui să dețină obiectul. În acest caz, la fiecare reconstruire a părintelui, obiectul va fi recreat, ceea ce duce la pierderea tuturor datelor acumulate. Această greșeală este deosebit de periculoasă în ierarhii complexe cu NavigationStack sau TabView.
Pentru a evita aceste probleme, urmează regula simplă: un @StateObject pentru o sursă de adevăr. Dacă datele trebuie să fie comune între mai multe ecrane — creează @StateObject o dată în View-ul rădăcină și transmite-l prin @ObservedObject sau @EnvironmentObject elementelor copil.
// ❌ Greșit: @ObservedObject pentru deținerea unui obiect
struct BadView: View {
@ObservedObject var vm = ViewModel() // va fi recreat la fiecare actualizare!
}
// ✅ Corect: @StateObject pentru deținere
struct GoodView: View {
@StateObject var vm = ViewModel() // creat o dată pentru durata de viață a View
}
Întrebări frecvente
@State lucrează cu tipuri valoare (structuri, șiruri, numere) și stochează valoarea direct în depozitul SwiftUI. @StateObject lucrează cu tipuri referință — clase conforme cu ObservableObject. @State este potrivit pentru stări locale simple, @StateObject — pentru obiecte complexe cu logică și proprietăți publicate.
Nu, @StateObject este disponibil doar de la iOS 14 în sus. Pentru iOS 13 folosește @ObservedObject și creează ObservableObject în View-ul părinte prin @State cu gestionare manuală a ciclului de viață. O alternativă — folosește @State cu struct în loc de class pentru datele care nu necesită semantică de referință.
View-ul copil își va crea propria copie a ObservableObject, complet independentă de cea a părintelui. Modificările într-una nu se vor reflecta în cealaltă. Aceasta este aproape întotdeauna o greșeală: folosește @ObservedObject pentru a primi obiectul de la părinte și @StateObject doar pentru a crea un obiect nou în interiorul View.
Obiectul este distrus când View-ul care l-a creat este complet eliminat din ierarhia SwiftUI. Pentru un ecran din NavigationStack, aceasta se întâmplă la pop de pe stiva de navigare. Pentru o fereastră modală — la închiderea acesteia. Pentru TabView — la schimbarea filei, dacă View nu este stocat în cache.
Folosește un init personalizat cu acces la property wrapper prin underscore: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Acest model permite transmiterea oricăror parametri către ObservableObject, păstrând în același timp garanția creării unice a obiectului pe durata de viață a View.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și