Introduction
Depuis les premières versions de SwiftUI, la gestion de l’état repose sur un ensemble de mécanismes qui, bien que puissants, ont rapidement montré certaines limites dans des projets concrets. Entre ObservableObject, @Published, @StateObjectou encore @ObservedObject, il n’était pas toujours évident de comprendre précisément comment et pourquoi une vue se mettait à jour.
Dans la continuité de mon développement de mon application macOS de nettoyage de caches développeur, je poursuis mon exploration de SwiftUI et de ses évolutions récentes. Ce type de projet concret met rapidement en lumière les limites des anciens mécanismes, notamment dès que l’état devient un peu plus complexe ou partagé entre plusieurs vues.
Avec l’arrivée d’iOS 17, Apple a décidé de repenser en profondeur ce système en introduisant le framework Observation et le macro @Observable. Derrière cette évolution se cache un objectif clair : rendre l’observation des données plus fine, plus performante et surtout plus intuitive à utiliser au quotidien.
Dans cet article, nous allons prendre le temps de comprendre ce qui change réellement, pourquoi ce nouveau modèle simplifie le développement SwiftUI, et comment adapter votre code existant sans introduire de complexité inutile.
Une nouvelle philosophie : @Observable dans SwiftUI
Là où ObservableObject reposait sur une logique relativement globale, @Observable introduit une approche beaucoup plus précise, basée sur l’utilisation réelle des données dans les vues.
Concrètement, cela signifie que SwiftUI ne va plus observer un objet dans son ensemble, mais uniquement les propriétés qui sont effectivement utilisées dans le body d’une vue. Cette différence peut sembler subtile au premier abord, mais elle change profondément la manière dont les mises à jour sont déclenchées.
Prenons un exemple simple pour illustrer ce comportement :
@Observable
class CounterViewModel {
var count: Int = 0
var title: String = "Compteur"
}Dans une vue SwiftUI :
struct CounterView: View {
@State private var viewModel = CounterViewModel()
var body: some View {
Text("\(viewModel.count)")
}
}Dans ce cas précis, seule la propriété count est réellement observée par SwiftUI. Si title est modifiée, la vue ne sera pas redessinée, car elle n’est jamais utilisée dans le body. Ce comportement permet d’éviter des rafraîchissements inutiles, ce qui devient particulièrement intéressant dans des interfaces plus complexes.
Les limites de ObservableObject dans les projets réels
Pour bien mesurer l’apport de @Observable, il est utile de revenir sur les limites que l’on rencontre avec ObservableObjectdans un contexte réel.
Le premier point concerne le caractère global de l’observation. Dès qu’une propriété marquée avec @Published est modifiée, l’ensemble des vues qui observent l’objet sont notifiées, indépendamment de la propriété réellement utilisée. Cela peut rapidement entraîner des recalculs inutiles et dégrader les performances, notamment lorsque les modèles deviennent plus riches.
Un autre aspect concerne la verbosité du code. Entre la déclaration des propriétés avec @Published, l’utilisation de @StateObject pour gérer le cycle de vie, et @ObservedObject pour la propagation, on se retrouve avec un ensemble de règles qui alourdissent la lecture et rendent l’intention moins claire.
Exemple classique avec ObservableObject :
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
@Published var title: String = ""
}Même si seule la propriété count est utilisée dans la vue, une modification de title entraînera un rafraîchissement. Ce comportement peut sembler acceptable sur des cas simples, mais devient rapidement problématique à grande échelle.
Comprendre le tracking des propriétés dans SwiftUI
Le véritable changement introduit avec @Observable repose sur le mécanisme de tracking des accès aux propriétés. Plutôt que de notifier toutes les vues à chaque modification, SwiftUI enregistre précisément quelles propriétés sont utilisées lors de l’évaluation du body.
Ce mécanisme s’appuie sur le framework Observation, qui intercepte les lectures de propriétés. Lorsqu’une vue accède à une valeur, cette dépendance est enregistrée. Par la suite, seule une modification de cette propriété spécifique déclenchera une mise à jour.
Ce modèle permet d’obtenir un comportement beaucoup plus prévisible. Là où auparavant il était parfois difficile de comprendre pourquoi une vue se rafraîchissait, le lien entre l’état et le rendu devient désormais explicite.
Migrer un ViewModel vers @Observable
La migration depuis ObservableObject vers @Observable est généralement assez simple, mais elle implique de revoir certains changements.
Prenons un exemple classique :
class UserViewModel: ObservableObject {
@Published var name: String = "John"
@Published var age: Int = 30
}Dans une vue :
struct UserView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack {
Text(viewModel.name)
Text("\(viewModel.age)")
}
}
}Avec @Observable, la version devient beaucoup plus légère :
import Observation
@Observable
class UserViewModel {
var name: String = "John"
var age: Int = 30
}struct UserView: View {
@State private var viewModel = UserViewModel()
var body: some View {
VStack {
Text(viewModel.name)
Text("\(viewModel.age)")
}
}
}Le changement le plus notable est la disparition de @Published et le remplacement de @StateObject par @State. Cela peut surprendre au début, mais reflète une simplification du modèle. La vue devient simplement propriétaire de son état.
Bien comprendre @State, @Bindable et @Environment

L’introduction de @Observable ne se limite pas à un nouveau macro. Elle s’accompagne aussi d’une clarification importante dans la manière d’utiliser les property wrappers dans SwiftUI. Là où les anciennes approches pouvaient prêter à confusion, notamment entre @StateObject et @ObservedObject, le nouveau modèle propose une séparation beaucoup plus nette des responsabilités.
Pour bien tirer parti de ce système, il est essentiel de comprendre le rôle précis de @State, @Bindable et @Environment, ainsi que la manière dont ils s’articulent entre eux dans une architecture SwiftUI moderne.
@State : la source de vérité locale
Avec @Observable, @State devient le point d’entrée naturel pour instancier et posséder un modèle au sein d’une vue. Là où l’on utilisait auparavant @StateObject pour gérer le cycle de vie d’un ObservableObject, cette distinction disparaît complètement.
Lorsqu’une vue crée une instance d’un modèle observable, elle en devient responsable. SwiftUI se charge alors de conserver cette instance à travers les recompositions de la vue, tout en assurant la mise à jour de l’interface lorsque les propriétés utilisées changent.
@State private var viewModel = UserViewModel()Ce changement simplifie considérablement le modèle mental. Il n’y a plus besoin de se demander si un objet doit être observé ou simplement stocké : @State remplit désormais ces deux rôles de manière transparente.
Dans la pratique, cela signifie que toute donnée créée et contrôlée localement par une vue doit passer par @State. C’est elle qui devient la source de vérité principale dans ce contexte.
Une erreur fréquente lors de la migration consiste à continuer d’utiliser @StateObject. Avec @Observable, cela n’a plus lieu d’être et peut même introduire des comportements inattendus.
@Bindable : modifier un modèle depuis une sous-vue
Si @State permet de posséder une donnée, il ne suffit pas dès que l’on souhaite la modifier depuis une vue enfant. C’est précisément dans ce cas que @Bindable intervient.
Ce wrapper permet d’exposer un modèle observable sous forme de binding, ce qui rend possible l’utilisation de la syntaxe $ sur ses propriétés. Sans lui, SwiftUI ne peut pas générer automatiquement les bindings nécessaires pour des composants interactifs comme TextField.
struct EditView: View {
@Bindable var viewModel: UserViewModel
var body: some View {
TextField("Name", text: $viewModel.name)
}
}Dans cet exemple, la vue ne possède pas le modèle, mais elle a besoin de le modifier. @Bindable joue donc un rôle d’intermédiaire, en permettant à SwiftUI de suivre les mutations tout en conservant une observation fine des propriétés.
Ce point est particulièrement important dans les formulaires ou les écrans d’édition. Sans @Bindable, il devient rapidement difficile de maintenir un code propre et cohérent.
Un piège courant consiste à passer un modèle observable à une sous-vue sans utiliser @Bindable, puis à tenter d’accéder à $viewModel.property. Cela ne fonctionnera pas, car SwiftUI ne dispose pas des informations nécessaires pour créer le binding.
@Environment : partager un état global
Enfin, @Environment permet de gérer les données qui doivent être accessibles à plusieurs niveaux de la hiérarchie de vues, sans avoir à les passer explicitement de parent à enfant.
Avec @Observable, @EnvironmentObject est remplacé par une approche plus moderne et plus typée. On injecte directement une instance dans l’environnement, puis on la récupère en utilisant son type.
@main
struct MyApp: App {
@State private var viewModel = UserViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environment(viewModel)
}
}
}Puis dans une vue :
struct ContentView: View {
@Environment(UserViewModel.self) var viewModel
var body: some View {
Text(viewModel.name)
}
}Cette approche présente plusieurs avantages. Elle rend le code plus explicite, réduit les risques d’erreurs liés aux types, et s’intègre naturellement avec le nouveau système d’observation fine.
Cependant, il est important de ne pas abuser de @Environment. Même si son utilisation est très pratique, elle peut introduire un couplage fort entre les vues si elle est utilisée de manière excessive. Dans la plupart des cas, elle doit être réservée aux données réellement globales, comme une session utilisateur, des paramètres applicatifs ou un gestionnaire de navigation.
Retour d’expérience et pièges fréquents
L’adoption de @Observable dans SwiftUI simplifie clairement le code, mais elle demande aussi de changer certains réflexes hérités de ObservableObject. L’erreur la plus fréquente consiste à migrer sans revoir la manière dont l’état est structuré. Avec ce nouveau système, il devient essentiel de bien distinguer qui possède la donnée et qui la modifie, sous peine de perdre en lisibilité.
Un autre point d’attention concerne les bindings. Contrairement aux anciennes approches, l’intention doit être explicite. Oublier @Bindable dans une vue qui modifie un modèle est une erreur classique, qui peut rapidement bloquer l’implémentation.
Enfin, même si @Environment est plus simple à utiliser, il doit rester réservé aux données réellement globales. Une utilisation excessive peut introduire un couplage fort entre les vues et complexifier l’architecture.
Avec un peu de pratique, ce nouveau modèle devient rapidement naturel. Il permet non seulement de réduire le boilerplate, mais aussi de rendre le comportement des vues plus prévisible et plus facile à raisonner.
Conclusion
Avec @Observable, Apple introduit bien plus qu’un simple remplacement de ObservableObject. Ce nouveau système redéfinit la manière dont SwiftUI gère l’état, en mettant l’accent sur la précision, la performance et la simplicité.
Une fois les nouveaux concepts assimilés, le code devient plus lisible et plus naturel à écrire. Le lien entre les données et l’interface est plus direct, ce qui facilite la compréhension et la maintenance des applications.
Dans la majorité des cas, on peut résumer l’approche de la manière suivante : la vue qui crée la donnée utilise @State, les vues qui la modifient utilisent @Bindable, et les données globales passent par @Environment. Cette règle simple permet d’éviter la plupart des erreurs et de tirer pleinement parti du nouveau système.
Ressources utiles pour aller plus loin
Si vous souhaitez approfondir l’utilisation de @Observable en SwiftUI et comprendre plus en détail les choix d’Apple, voici quelques ressources officielles particulièrement intéressantes.
La documentation Apple propose un guide complet pour accompagner la transition depuis ObservableObject vers le nouveau système basé sur @Observable :
👉 Migrating from the ObservableObject protocol to the Observable macro
Pour une vision plus globale et comprendre les intentions derrière ce changement, la session de la WWDC 2023 dédiée à l’Observation est également très instructive :
👉 WWDC 2023 – Discover Observation in SwiftUI
Ces ressources permettent de compléter les exemples présentés dans cet article et d’aller plus loin dans l’adoption du nouveau modèle proposé par SwiftUI.



