@StateObject est un property wrapper dans SwiftUI qui crée et possède une instance d'ObservableObject tout au long du cycle de vie d'une View. Lorsqu'une View apparaît pour la première fois à l'écran, @StateObject initialise l'objet et le stocke jusqu'à ce que la View soit supprimée de la mémoire. Cela garantit que les données ne sont pas réinitialisées lors de la reconstruction de l'interface — par exemple, lors du changement de thème ou de la mise à jour de la View parente. Selon la Documentation Développeur Apple (2025), @StateObject doit être utilisé comme source principale de vérité (source of truth) pour ObservableObject dans la hiérarchie SwiftUI, tandis que les Views enfants reçoivent l'objet déjà créé via @ObservedObject ou @EnvironmentObject.
Points Clés
@StateObject est un property wrapper introduit dans iOS 14 qui permet à une View de créer et de posséder une instance d'une classe conforme au protocole ObservableObject. Contrairement à @State qui fonctionne avec des types valeur (structs), @StateObject est conçu pour les types référence — des classes qui peuvent notifier SwiftUI des modifications de leurs propriétés.
Lorsqu'une View utilise @StateObject var viewModel: MyViewModel, SwiftUI crée automatiquement une instance de MyViewModel lors de la première apparition de la View et la stocke dans un stockage spécial du framework. À chaque mise à jour de la View (par exemple, lorsque l'état parent change), SwiftUI ne recrée pas l'objet — il utilise l'instance existante jusqu'à ce que la View soit supprimée de la hiérarchie.
Selon Apple WWDC Session 10137 (2024), @StateObject résout le problème de perte de données qui existait dans iOS 13 lors de la reconstruction des Views, forçant les développeurs à créer ObservableObject dans la View parente et à le passer via l'initialiseur. Cela entraînait une duplication de code et le risque de recréer accidentellement l'objet.
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)
}
}
}
Le mécanisme de @StateObject est basé sur l'intégration de SwiftUI avec le framework Combine. Lorsqu'un ObservableObject marque ses propriétés avec l'attribut @Published, SwiftUI s'abonne automatiquement aux changements via le publisher intégré au protocole ObservableObject. Lorsqu'une propriété publiée change, l'objet envoie un signal via le publisher objectWillChange, ce qui déclenche un redessin de toutes les Views qui observent cet objet.
SwiftUI stocke l'instance d'ObservableObject dans un stockage spécial lié à une instance spécifique de View. Ce stockage est créé une fois lors du premier rendu et existe jusqu'à ce que la View soit détruite. C'est pourquoi @StateObject garantit la stabilité de la référence — SwiftUI gère la mémoire automatiquement, sans dépendre de l'initialiseur de la View.
Selon objc.io — Thinking in SwiftUI (2025), l'implémentation interne de @StateObject utilise un mécanisme similaire à @State mais pour les types référence : SwiftUI crée une enveloppe (boxing) autour de l'objet et gère son cycle de vie via son propre allocateur, optimisé pour les reconstructions fréquentes de la hiérarchie des Views.
La principale différence entre @StateObject et @ObservedObject réside dans la propriété de l'objet. @StateObject crée et stocke l'objet — il en est le propriétaire. @ObservedObject observe seulement l'objet qui a été créé ailleurs et passé via l'initialiseur ou une propriété.
| Caractéristique | @StateObject | @ObservedObject |
|---|---|---|
| Propriété | Crée et possède l'objet | Observe seulement |
| Initialisation | À l'intérieur de la View via init/défaut | Externe, passée via paramètre |
| Cycle de vie | Lié au cycle de vie de la View | Non contrôlé par la View |
| Recréation | N'est pas recréé lors de la mise à jour | Peut être remplacé extérieurement |
| Version iOS | iOS 14+ | iOS 13+ |
La règle est simple : si la View crée l'ObservableObject — utilisez @StateObject. Si la View reçoit seulement un objet déjà créé du parent — utilisez @ObservedObject. Violer cette règle entraîne soit une perte de données (si @ObservedObject est utilisé pour la propriété), soit une création excessive d'objets (si @StateObject est utilisé pour l'observation).
@StateObject doit être utilisé dans les Views qui sont la source de vérité pour un ensemble spécifique de données. Les scénarios typiques incluent les écrans avec leur propre view model, les écrans racines des piles de navigation et les présentations modales gérant leur propre état.
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")
}
}
}
Initialiser @StateObject avec des paramètres nécessite une syntaxe spéciale, car SwiftUI gère lui-même la création de l'objet. Vous ne pouvez pas simplement passer des paramètres à l'initialiseur — vous devez utiliser une closure échappée ou une méthode d'usine séparée.
Selon Swift by Sundell (2024), l'approche la plus propre est d'utiliser une méthode d'usine ou une closure que SwiftUI appellera lors de la première création de l'objet. Une approche alternative consiste à initialiser l'ObservableObject dans la View parente et à le passer via @StateObject en utilisant l'initialiseur 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)
}
}
Il est important de se rappeler que l'initialiseur de View avec @StateObject doit utiliser un underscore avant le nom de la propriété (_viewModel) pour accéder au property wrapper lui-même, et non à sa valeur. C'est un modèle Swift standard pour travailler avec les property wrappers dans les initialiseurs.
L'erreur la plus courante est d'utiliser @ObservedObject au lieu de @StateObject pour une View qui devrait posséder l'objet. Dans ce cas, chaque fois que le parent est reconstruit, l'objet sera recréé, entraînant la perte de toutes les données accumulées. Cette erreur est particulièrement insidieuse dans les hiérarchies complexes avec NavigationStack ou TabView.
Pour éviter ces problèmes, suivez une règle simple : un @StateObject par source de vérité. Si les données doivent être partagées entre plusieurs écrans — créez @StateObject une fois dans la View racine et passez-le via @ObservedObject ou @EnvironmentObject aux éléments enfants.
// ❌ 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
}
Foire Aux Questions
@State fonctionne avec les types valeur (structs, chaînes, nombres) et stocke la valeur directement dans le stockage SwiftUI. @StateObject fonctionne avec les types référence — les classes conformes à ObservableObject. @State convient pour les états locaux simples, @StateObject pour les objets complexes avec logique et propriétés publiées.
Non, @StateObject n'est disponible qu'à partir d'iOS 14. Pour iOS 13, utilisez @ObservedObject et créez l'ObservableObject dans la View parente via @State avec une gestion manuelle du cycle de vie. Une alternative est d'utiliser @State avec un struct au lieu d'une classe pour les données qui ne nécessitent pas de sémantique de référence.
La View enfant créera sa propre copie de l'ObservableObject, complètement indépendante de celle du parent. Les modifications dans l'une n'affecteront pas l'autre. C'est presque toujours une erreur : utilisez @ObservedObject pour recevoir un objet du parent et @StateObject uniquement pour créer un nouvel objet à l'intérieur de la View.
L'objet est détruit lorsque la View qui l'a créé est complètement supprimée de la hiérarchie SwiftUI. Pour un écran dans NavigationStack, cela se produit lors du pop de la pile de navigation. Pour une fenêtre modale — lorsqu'elle est fermée. Pour TabView — lors du changement d'onglet, si la View n'est pas mise en cache.
Utilisez un init personnalisé avec un accès au property wrapper via underscore : _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Ce modèle permet de passer n'importe quels paramètres à l'ObservableObject tout en conservant la garantie de création unique de l'objet pendant la durée de vie de la View.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi