@StateObject: cos’è, differenza da @ObservedObject ed esempi

Autore: IT Sectr Pubblicato: 2026-06-19 Tempo di lettura: 7 min

@StateObject è un Property Wrapper in SwiftUI per creare e possedere un’istanza ObservableObject direttamente in una view. SwiftUI garantisce che l’oggetto venga inizializzato una volta per ciclo di vita della vista e non venga ricreato in rendering successivi. Secondo la Documentazione Sviluppatori Apple (2025), @StateObject è raccomandato per le viste radice che creano una fonte di dati. @StateObject è la scelta giusta per possedere un ObservableObject nella gerarchia SwiftUI.

Punti chiave

  • @StateObject — Property Wrapper per creare e possedere ObservableObject in una view
  • Istanza unica — l’oggetto viene creato una volta e non viene ricreato nei rendering
  • Fonte di verità — @StateObject garantisce la stabilità dei dati per l’intera gerarchia
  • Differenza da @ObservedObject — @ObservedObject non possiede l’oggetto e può perderlo
  • Viste radice — @StateObject viene usato nella vista che crea l’oggetto

Cos’è @StateObject in SwiftUI?

@StateObject è un Property Wrapper introdotto in SwiftUI 2.0 (iOS 14) che combina le capacità di @ObservedObject e @State. Come @ObservedObject, si sottoscrive ai cambiamenti di ObservableObject. Come @State, garantisce che i dati sopravvivano a inizializzazioni ripetute della struttura della vista. @StateObject crea l’oggetto una volta quando la vista appare per la prima volta e lo memorizza nell’heap di SwiftUI.

Prima di @StateObject, gli sviluppatori usavano @ObservedObject per tutti gli ObservableObject, inclusi quelli creati nelle viste. Ciò portava a frequenti perdite di dati quando la vista padre veniva aggiornata, causando la ricreazione della struttura della vista e portando via l’istanza @ObservedObject con sé. @StateObject ha risolto questo problema aggiungendo una garanzia di stabilità.

La regola principale: @StateObject viene usato nella vista che crea l’oggetto nell’inizializzatore predefinito (let model = ViewModel()). Le viste figlie che ricevono questo oggetto usano @ObservedObject. Questa separazione garantisce un’unica fonte di verità in tutta la gerarchia.

Ciclo di vita di @StateObject

SwiftUI gestisce il ciclo di vita di @StateObject tramite un gestore di archiviazione simile a @State. Quando la vista appare per la prima volta, SwiftUI alloca memoria per l’oggetto e lo memorizza in un’area persistente. Nei rendering successivi (chiamate body), l’oggetto non viene ricreato—viene utilizzata l’istanza esistente. L’oggetto vive finché la vista è nella gerarchia.

Quando la vista viene rimossa dalla gerarchia, SwiftUI distrugge il @StateObject, chiamando deinit. Quando la vista viene aggiunta nuovamente alla gerarchia, viene creata una nuova istanza. È importante considerarlo durante la progettazione: se è necessario preservare i dati tra le rimozioni della vista, utilizzare un livello di servizio (singleton o DI) o @AppStorage per la persistenza.

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() }
    }
}

Nell’esempio, TimerViewModel viene creato tramite @StateObject e vive finché TimerView è sullo schermo. Il timer si avvia in onAppear e si ferma in deinit. Se si usasse @ObservedObject, ogni rendering di TimerView creerebbe un nuovo TimerViewModel con secondi = 0, e il timer non funzionerebbe mai correttamente. @StateObject garantisce che il viewModel sia unico e stabile.

@StateObject vs @ObservedObject: confronto

La scelta tra @StateObject e @ObservedObject dipende da chi possiede l’oggetto. Se la vista crea l’oggetto—@StateObject. Se la vista riceve un oggetto già pronto—@ObservedObject. Questa regola è così importante che Xcode mostra un avviso quando si usa @StateObject in una vista figlia che riceve l’oggetto tramite un inizializzatore.

SituazioneWrapper consigliato
La vista crea il modello tramite ViewModel()@StateObject
La vista riceve il modello dal padre@ObservedObject
Il modello viene usato in una vista@StateObject
Il modello viene passato tramite Environment@EnvironmentObject
Il modello è necessario per le anteprime@ObservedObject + mock

In pratica, all’inizio di un progetto, @StateObject viene spesso utilizzato nella vista radice e @ObservedObject in tutte le viste figlie. Con la crescita dell’applicazione, alcune istanze di @StateObject possono essere sostituite con @EnvironmentObject per semplificare la gerarchia. Tuttavia, @StateObject rimane la scelta migliore per schermi modulari con logica propria.

Pattern di utilizzo di @StateObject

Il primo pattern—MVVM con @StateObject. Il ViewModel come ObservableObject viene creato nella vista tramite @StateObject. Il ViewModel contiene proprietà @Published e logica di business. La vista si sottoscrive ai cambiamenti e aggiorna l’interfaccia. Questo approccio fornisce un isolamento testabile: il ViewModel può essere testato senza UI creando un’istanza direttamente.

Il secondo pattern—@StateObject con dipendenze. Se il ViewModel richiede servizi, utilizzare l’inizializzazione con parametri. Ad esempio, @StateObject var viewModel = UserViewModel(api: APIClient.shared). Tuttavia, fare attenzione: i parametri vengono calcolati a ogni rendering di body, ma l’oggetto viene creato solo una volta. SwiftUI ignora le inizializzazioni successive di @StateObject.

Il terzo pattern—@StateObjects annidati. In SwiftUI, è possibile avere più @StateObjects in una vista, ma ciò è raramente giustificato. Di solito un @StateObject gestisce l’intero set di dati della vista. Se la logica diventa troppo complessa, suddividerla in una composizione di servizi @ObservedObject all’interno di un @StateObject.

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

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

Nell’esempio, AppView crea due @StateObjects: NavigationRouter per gestire la navigazione e AuthViewModel per l’autenticazione. Entrambi gli oggetti vengono iniettati nell’Environment tramite environmentObject. Qualsiasi vista figlia può accedervi tramite @EnvironmentObject senza passare attraverso la catena di inizializzatori.

@StateObject e inizializzazione con parametri

@StateObject supporta l’inizializzazione con qualsiasi parametro, ma con un’importante avvertenza: l’inizializzatore viene chiamato solo una volta. Nei successivi rendering di body, i nuovi valori dei parametri vengono ignorati. Ciò significa che se si passa @State var id: Int = 5 a @StateObject var vm = ViewModel(id: id), quando id cambia, il ViewModel non riceverà il nuovo valore.

Per risolvere questo problema, utilizzare onReceive o onAppear per la sincronizzazione. Sottoscriversi ai cambiamenti dei parametri all’interno del ViewModel tramite Combine o passare i parametri tramite il metodo .onChange(of:) a livello di vista. Un’alternativa è usare @ObservedObject invece di @StateObject se l’oggetto deve rispondere dinamicamente ai cambiamenti esterni.

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

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

L’approccio corretto: DetailView riceve itemId come proprietà let (passata tramite l’inizializzatore della struttura), e @StateObject crea DetailViewModel senza parametri. In onAppear, viene chiamato il metodo load(id:) per caricare i dati per l’ID ricevuto. Ciò garantisce che il ViewModel sia creato dal meccanismo @StateObject, ma i dati vengono caricati a ogni comparsa della vista con l’ID corrente.

Errori comuni con @StateObject

L’errore principale—usare @StateObject nelle viste figlie che ricevono l’oggetto da un padre. Se ParentView crea @StateObject model e ChildView dichiara @StateObject var model: ModelType (con un parametro predefinito), ChildView creerà la propria istanza indipendente. Gli oggetti padre e figlio non saranno connessi e i cambiamenti in uno non si rifletteranno nell’altro.

Il secondo errore—posizionare @StateObject in List o ForEach. Ogni elemento dell’elenco crea il proprio @StateObject, portando a molteplici istanze indipendenti. Per gli elenchi, l’approccio corretto è passare un singolo ObservableObject a tutti gli elementi tramite @ObservedObject o utilizzare strutture Identifiable con @State all’interno di List.

Il terzo problema—mancanza di pulizia in deinit. @StateObject vive per l’intero ciclo di vita della vista. Se l’oggetto crea timer, abbonamenti Combine o richieste di rete, deinit deve annullarli. Altrimenti, perdite di memoria e lavoro in background continuativo dopo la chiusura dello schermo sono inevitabili. Utilizzare sempre un archivio Cancellable di Combine o invalidare i timer in deinit.

Domande frequenti

Quando è stato introdotto @StateObject in SwiftUI?

@StateObject è stato aggiunto in SwiftUI 2.0 alla WWDC 2020 insieme a iOS 14, macOS 11, watchOS 7 e tvOS 14. Prima di ciò, @ObservedObject era l’unico modo per lavorare con ObservableObject, portando spesso a bug di perdita di dati.

@StateObject può essere opzionale?

No, @StateObject non supporta i tipi Optional. L’oggetto deve essere inizializzato al momento della dichiarazione. Se è necessario un oggetto opzionale, utilizzare @ObservedObject o @EnvironmentObject con un tipo opzionale.

Come verificare che @StateObject sia creato solo una volta?

Aggiungere print(#function) all’inizializzatore e al deinit di ObservableObject. Se init non viene chiamato nei rendering—@StateObject funziona correttamente. Se init viene chiamato ogni volta—sostituire @ObservedObject con @StateObject.

@StateObject può essere usato con UIKit tramite UIHostingController?

Sì, @StateObject funziona nelle viste SwiftUI incorporate in UIKit tramite UIHostingController. Il ciclo di vita dell’oggetto è legato alla vista SwiftUI, non al UIViewController. Se la vista SwiftUI viene sostituita, @StateObject viene distrutto.

Cosa è meglio: un @StateObject con un ViewModel grande o più piccoli?

Più @StateObjects piccoli con responsabilità separate. Ciò migliora la testabilità, la riutilizzabilità e le prestazioni—quando un oggetto cambia, solo le parti sottoscritte dell’interfaccia vengono ridisegnate, non l’intera vista.

Riepilogo

  • @StateObject — Property Wrapper per creare e possedere ObservableObject in una view
  • Istanza unica — l’oggetto non viene ricreato nei successivi rendering di body
  • Fonte di verità — @StateObject nella vista radice garantisce la stabilità dei dati per la gerarchia
  • Regola di selezione — @StateObject per creare, @ObservedObject per ricevere un oggetto già pronto
  • Inizializzazione — i parametri in @StateObject vengono calcolati una volta, gli aggiornamenti non vengono tracciati
  • Deinit — pulizia obbligatoria di timer e abbonamenti nel deinit di ObservableObject
  • iOS 14+ — @StateObject è disponibile da iOS 14, macOS 11, watchOS 7, tvOS 14

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.

Discuti il progetto

Leggi anche