@ObservedObject est un property wrapper dans SwiftUI qui permet à une View d’observer les changements dans un ObservableObject créé ailleurs dans la hiérarchie. Contrairement à @StateObject, @ObservedObject ne crée pas l’objet — il s’abonne seulement à son publisher objectWillChange et redessine la View lorsque les propriétés publiées sont mises à jour. Cela fait de @ObservedObject le bon choix pour les Views enfants qui reçoivent des données d’un parent via un initialiseur. Selon un article de Paul Hudson — Hacking with Swift (2025), une architecture typique d’application SwiftUI se construit ainsi : la View racine utilise @StateObject pour créer un view model, et toutes les Views enfants le reçoivent via @ObservedObject, garantissant une source unique de vérité sans duplication de données.
Points clés
@ObservedObject est un property wrapper qui abonne une View aux changements ObservableObject. Lorsqu’un objet marqué avec @ObservedObject modifie l’une de ses propriétés déclarées avec @Published, SwiftUI redessine automatiquement la View. @ObservedObject ne crée pas l’objet — il établit seulement une connexion entre une instance ObservableObject existante et la View qui doit réagir à ses changements.
La différence clé entre @ObservedObject et @StateObject est la propriété. @ObservedObject suppose que l’objet est créé et stocké quelque part plus haut dans la hiérarchie des Views. La View enfant reçoit une référence à cet objet via l’initialiseur et l’observe simplement. Si la View enfant est recréée, elle reçoit la même référence du parent — les données ne sont pas perdues.
Selon la Documentation Apple Developer — SwiftUI (2025), @ObservedObject est disponible à partir d’iOS 13, ce qui en fait la seule option pour observer ObservableObject dans les projets prenant en charge les anciennes versions d’iOS. Dans iOS 14+, @StateObject est préféré pour créer des objets, mais @ObservedObject reste pertinent pour transmettre des objets existants.
Le mécanisme de @ObservedObject est basé sur le protocole ObservableObject du framework Combine. Chaque classe conforme à ObservableObject obtient automatiquement un publisher objectWillChange qui envoie un signal avant toute modification d’une propriété @Published. SwiftUI s’abonne à ce publisher via @ObservedObject et, à la réception du signal, marque la View comme nécessitant un redessin.
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// récupérer les données
isLoading = false
}
}
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel
var body: some View {
List(viewModel.tasks) { task in
Text(task.title)
}
.task { await viewModel.loadTasks() }
}
}
Lorsque la View parent transmet viewModel à TaskListView via l’initialiseur, SwiftUI crée une connexion entre l’objet et la View. Lorsque le tableau tasks ou le flag isLoading change, SwiftUI redessine TaskListView. L’objet lui-même reste inchangé — il est stocké dans la View parent via @StateObject.
La différence entre @ObservedObject et @StateObject est la différence entre un observateur et un propriétaire. @StateObject crée l’objet et gère son cycle de vie. @ObservedObject observe seulement un objet qui a été créé et stocké ailleurs. Le choix entre eux est déterminé par la responsabilité de la View vis-à-vis des données.
| Scénario | Recommandation | Raison |
|---|---|---|
| La View crée des données | @StateObject | La View possède l’objet et est responsable de son cycle de vie |
| La View reçoit des données | @ObservedObject | La View observe seulement, l’objet vit dans le parent |
| Support iOS 13 | @ObservedObject | @StateObject indisponible, utilisez @ObservedObject avec gestion manuelle |
| Composant réutilisable | @ObservedObject | Le composant ne doit pas créer de données — il les reçoit de l’extérieur |
La règle principale : si la View crée l’objet — @StateObject. Si la View reçoit l’objet — @ObservedObject. Violer cette règle en utilisant @ObservedObject pour créer un objet entraîne une perte de données lors de la reconstruction de la View. La violer en utilisant @StateObject pour recevoir un objet crée une instance dupliquée indépendante du parent.
Un scénario d’utilisation typique de @ObservedObject est une liste de tâches où la View racine crée un view model et chaque cellule de liste le reçoit via @ObservedObject. Chaque cellule peut appeler des méthodes du view model, et les changements sont automatiquement reflétés dans toute la liste puisque toutes les cellules observent le même objet.
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("Terminé") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
Dans cet exemple, TaskListContainer crée viewModel via @StateObject, et chaque TaskRow le reçoit via @ObservedObject. Lorsque l’utilisateur appuie sur « Done » dans n’importe quelle ligne, viewModel.completeTask modifie une propriété publiée, et toutes les Views observant cet objet se mettent à jour automatiquement.
L’erreur la plus courante est d’utiliser @ObservedObject pour créer un objet à l’intérieur d’une View. Lorsque la View est reconstruite (par exemple, lorsque l’état change), SwiftUI crée une nouvelle instance ObservableObject, entraînant la perte de toutes les données accumulées. Cette erreur est particulièrement douloureuse dans NavigationStack, où un utilisateur pourrait remplir un formulaire et perdre les données en revenant en arrière.
// ❌ Perte de données : @ObservedObject ne conserve pas l’objet
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// Nouveau formVM créé à chaque reconstruction de vue !
}
// ✅ Correct : @StateObject conserve l’objet
struct FormView: View {
@StateObject var formVM = FormViewModel()
// Objet créé une fois par durée de vie de la vue
}
Si une View enfant déclare le même ObservableObject via @StateObject, elle crée une copie indépendante. Les changements dans l’objet parent ne seront pas visibles dans l’enfant, et vice versa. Utilisez toujours @ObservedObject pour les Views enfants qui reçoivent l’objet de l’extérieur.
Dans SwiftUI moderne, il existe plusieurs alternatives à @ObservedObject, chacune avec ses avantages. Le choix dépend de l’architecture de l’application, de la version d’iOS et du cas d’utilisation spécifique.
Le choix entre @ObservedObject et @EnvironmentObject est une question de style et d’architecture. @ObservedObject montre explicitement les dépendances de la View via l’initialiseur, rendant le code plus prévisible. @EnvironmentObject est pratique pour les hiérarchies profondes mais cache les dépendances, ce qui peut compliquer le débogage.
Questions fréquentes
Oui, si l’objet est créé et stocké en dehors de SwiftUI — par exemple, dans un AppDelegate ou un singleton. Dans ce cas, @ObservedObject s’abonne simplement aux changements d’un objet existant. Cependant, pour les objets créés dans la hiérarchie SwiftUI, @StateObject est toujours nécessaire quelque part au-dessus.
La raison la plus probable est que la propriété est modifiée pas via @Published ou que l’objet lui-même n’est pas modifié mais sa structure interne mute sans appeler objectWillChange. Pour les collections, utilisez l’affectation d’une nouvelle copie : array.append() ne suffit pas — vous devez réaffecter le tableau lui-même via array = array + [element].
@ObservedObject lui-même ne crée pas de surcharge significative. Les problèmes surviennent avec les changements fréquents de propriétés @Published — chaque changement déclenche un redessin de toutes les Views observatrices. Pour optimiser, utilisez EquatableView, réduisez le nombre de propriétés publiées et évitez les mises à jour inutiles.
@ObservedObject observe une classe ObservableObject entière et redessine la View lors de tout changement de ses propriétés publiées. @Binding crée une connexion bidirectionnelle avec une valeur spécifique (String, Int, Bool) et permet de la lire et de l’écrire. @Binding est plus léger et ne nécessite pas ObservableObject.
Oui, c’est le motif standard. @Published dans ObservableObject s’intègre automatiquement avec @ObservedObject. Chaque propriété @Published ajoute un observateur au publisher objectWillChange. Lorsque l’une d’elles change, toutes les Views avec @ObservedObject pour cet objet sont redessinées.
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