@StateObject è un property wrapper in SwiftUI che crea e possiede un'istanza di ObservableObject per l'intero ciclo di vita di una View. Quando una View appare per la prima volta sullo schermo, @StateObject inizializza l'oggetto e lo conserva fino a quando la View non viene rimossa dalla memoria. Questo garantisce che i dati non vengano reimpostati durante la ricostruzione dell'interfaccia — ad esempio, quando si cambia tema o si aggiorna la View padre. Secondo la Documentazione per Sviluppatori Apple (2025), @StateObject dovrebbe essere utilizzato come fonte primaria di verità (source of truth) per ObservableObject nella gerarchia SwiftUI, mentre le Views figlie ricevono l'oggetto già creato tramite @ObservedObject o @EnvironmentObject.
Punti Chiave
@StateObject è un property wrapper introdotto in iOS 14 che permette a una View di creare e possedere un'istanza di una classe conforme al protocollo ObservableObject. A differenza di @State, che lavora con tipi valore (struct), @StateObject è progettato per tipi riferimento — classi che possono notificare SwiftUI sui cambiamenti delle loro proprietà.
Quando una View usa @StateObject var viewModel: MyViewModel, SwiftUI crea automaticamente un'istanza di MyViewModel quando la View appare per la prima volta e la conserva in un archivio speciale del framework. Ad ogni aggiornamento della View (ad esempio, quando lo stato padre cambia), SwiftUI non ricrea l'oggetto — utilizza l'istanza esistente fino a quando la View non viene rimossa dalla gerarchia.
Secondo Apple WWDC Session 10137 (2024), @StateObject risolve il problema di perdita di dati che esisteva in iOS 13 quando le Views venivano ricostruite, costringendo gli sviluppatori a creare ObservableObject nella View padre e passarlo tramite l'inizializzatore. Questo portava a duplicazione di codice e al rischio di ricreare accidentalmente l'oggetto.
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("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
Il meccanismo di @StateObject si basa sull'integrazione di SwiftUI con il framework Combine. Quando un ObservableObject contrassegna le sue proprietà con l'attributo @Published, SwiftUI si iscrive automaticamente ai cambiamenti tramite il publisher integrato nel protocollo ObservableObject. Quando una proprietà pubblicata cambia, l'oggetto invia un segnale tramite il publisher objectWillChange, che attiva un ridisegno di tutte le Views che osservano questo oggetto.
SwiftUI conserva l'istanza di ObservableObject in un archivio speciale legato a una specifica istanza di View. Questo archivio viene creato una volta durante il primo rendering ed esiste fino a quando la View non viene distrutta. Ecco perché @StateObject garantisce la stabilità del riferimento — SwiftUI gestisce la memoria automaticamente, senza dipendere dall'inizializzatore della View.
Secondo objc.io — Thinking in SwiftUI (2025), l'implementazione interna di @StateObject utilizza un meccanismo simile a @State ma per tipi riferimento: SwiftUI crea un involucro (boxing) attorno all'oggetto e gestisce il suo ciclo di vita tramite il proprio allocatore, ottimizzato per frequenti ricostruzioni della gerarchia delle Views.
La differenza principale tra @StateObject e @ObservedObject sta in chi possiede l'oggetto. @StateObject crea e conserva l'oggetto — ne è il proprietario. @ObservedObject osserva solo l'oggetto che è stato creato altrove e passato tramite inizializzatore o proprietà.
| Caratteristica | @StateObject | @ObservedObject |
|---|---|---|
| Proprietà | Crea e possiede l'oggetto | Osserva solo |
| Inizializzazione | Dentro la View tramite init/default | Esterna, passata tramite parametro |
| Ciclo di vita | Legato al ciclo di vita della View | Non controllato dalla View |
| Ricreazione | Non viene ricreato all'aggiornamento | Può essere sostituito esternamente |
| Versione iOS | iOS 14+ | iOS 13+ |
La regola è semplice: se la View crea l'ObservableObject — usa @StateObject. Se la View riceve solo un oggetto già creato dal padre — usa @ObservedObject. Violare questa regola porta o alla perdita di dati (se si usa @ObservedObject per la proprietà) o alla creazione eccessiva di oggetti (se si usa @StateObject per l'osservazione).
@StateObject dovrebbe essere usato nelle Views che sono la fonte di verità per un insieme specifico di dati. Gli scenari tipici includono schermate con il proprio view model, schermate radice di stack di navigazione e presentazioni modali che gestiscono il proprio stato.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Inizializzare @StateObject con parametri richiede una sintassi speciale, poiché SwiftUI gestisce la creazione dell'oggetto autonomamente. Non puoi semplicemente passare parametri all'inizializzatore — devi utilizzare una closure di escape o un metodo factory separato.
Secondo Swift by Sundell (2024), l'approccio più pulito è utilizzare un metodo factory o una closure che SwiftUI chiamerà quando l'oggetto viene creato per la prima volta. Un approccio alternativo è inizializzare l'ObservableObject nella View padre e passarlo tramite @StateObject utilizzando l'inizializzatore 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)
}
}
È importante ricordare che l'inizializzatore di View con @StateObject dovrebbe usare un underscore prima del nome della proprietà (_viewModel) per accedere al property wrapper stesso, non al suo valore. Questo è un pattern Swift standard per lavorare con i property wrapper negli inizializzatori.
L'errore più comune è usare @ObservedObject invece di @StateObject per una View che dovrebbe possedere l'oggetto. In questo caso, ogni volta che il padre viene ricostruito, l'oggetto verrà ricreato, portando alla perdita di tutti i dati accumulati. Questo errore è particolarmente insidioso in gerarchie complesse con NavigationStack o TabView.
Per evitare questi problemi, segui una regola semplice: un @StateObject per fonte di verità. Se i dati devono essere condivisi tra più schermate — crea @StateObject una volta nella View radice e passalo tramite @ObservedObject o @EnvironmentObject agli elementi figli.
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
Domande Frequenti
@State lavora con tipi valore (struct, stringhe, numeri) e conserva il valore direttamente nell'archivio SwiftUI. @StateObject lavora con tipi riferimento — classi conformi a ObservableObject. @State è adatto per stati locali semplici, @StateObject per oggetti complessi con logica e proprietà pubblicate.
No, @StateObject è disponibile solo da iOS 14 in poi. Per iOS 13, usa @ObservedObject e crea l'ObservableObject nella View padre tramite @State con gestione manuale del ciclo di vita. Un'alternativa è usare @State con un struct invece di una classe per dati che non richiedono semantica di riferimento.
La View figlia creerà la propria copia dell'ObservableObject, completamente indipendente da quella del padre. I cambiamenti in una non influenzeranno l'altra. Questo è quasi sempre un errore: usa @ObservedObject per ricevere un oggetto dal padre e @StateObject solo per creare un nuovo oggetto all'interno della View.
L'oggetto viene distrutto quando la View che lo ha creato viene completamente rimossa dalla gerarchia SwiftUI. Per una schermata in NavigationStack, ciò accade quando si fa pop dallo stack di navigazione. Per una finestra modale — quando viene chiusa. Per TabView — quando si cambia scheda, se la View non è memorizzata nella cache.
Usa un init personalizzato con accesso al property wrapper tramite underscore: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Questo pattern permette di passare qualsiasi parametro all'ObservableObject, mantenendo al contempo la garanzia di creazione singola dell'oggetto durante la vita della View.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche