Property Wrapper : concepts clés, syntaxe et exemples dans SwiftUI

Auteur : IT Sectr Publié le : 2026-06-19 Temps de lecture : 8 min

Property Wrapper est un mécanisme Swift qui ajoute une couche de logique à l'accès et à la modification des propriétés sans dupliquer le code. Dans SwiftUI, les Property Wrappers sont devenus la base de la gestion d'état des vues : @State, @Binding, @ObservedObject, @StateObject et @Environment. Selon la documentation Swift (2025), les wrappers de propriétés réduisent le code boilerplate dans les projets de 40% en moyenne. Comprendre Property Wrapper est essentiel pour tout développeur iOS souhaitant travailler efficacement avec le framework.

Points essentiels

  • Property Wrapper — une construction syntaxique Swift qui ajoute de la logique au getter et setter d'une propriété
  • @State — wrapper pour l'état local appartenant à une seule vue
  • @Binding — connexion bidirectionnelle avec une source de données de la vue parente
  • @ObservedObject — observation d'un ObservableObject externe sans possession
  • @StateObject — création et possession d'un ObservableObject au niveau de la vue actuelle

Qu'est-ce que Property Wrapper ?

Property Wrapper — une construction du langage Swift, introduite dans la version 5.1, qui permet d'encapsuler la logique d'accès aux propriétés dans un type séparé. Au lieu d'écrire des getters et setters répétitifs dans chaque classe, le développeur déclare le wrapper une fois et l'applique via l'annotation @ devant le type. Swift encapsule automatiquement la propriété dans le type spécifié, appelant ses méthodes wrappedValue et projectedValue lors de la lecture et de l'écriture. Selon Apple (WWDC 2019), les Property Wrappers sont devenus une abstraction clé pour SwiftUI.

Un wrapper de propriété est une structure ou une classe avec l'attribut @propertyWrapper. À l'intérieur, un tel type doit implémenter la propriété wrappedValue, qui retourne et définit la valeur réelle. Le compilateur Swift remplace les accès à la propriété d'origine par des appels à wrappedValue, cachant complètement l'implémentation du code appelant. De plus, on peut définir projectedValue — une projection accessible via le symbole $.

L'avantage des Property Wrappers réside dans la réutilisabilité de la logique. Par exemple, on peut créer un wrapper pour la validation d'email, la mise en cache de valeurs ou la synchronisation avec le stockage — et l'appliquer à n'importe quelle propriété du projet. Dans SwiftUI, ce concept est utilisé partout : chaque mécanisme de gestion d'état est implémenté comme un Property Wrapper séparé.

Comment fonctionne Property Wrapper dans Swift ?

En déclarant une propriété avec l'annotation @WrapperType var value: T, le compilateur Swift transforme le code. Il crée une instance de WrapperType et génère l'accès à la propriété via wrappedValue. Le code source let x = value devient let x = _value.wrappedValue, et value = newValue devient _value.wrappedValue = newValue. Cette transformation se produit à la compilation, sans surcharge à l'exécution.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

    var wrappedValue: String {
        get { text }
        set { text = newValue.capitalized }
    }

    init(initialValue: String) {
        text = initialValue.capitalized
    }
}

Le listing montre le wrapper Capitalized, qui convertit automatiquement une chaîne en format avec majuscule. Lors de l'assignation d'une valeur, le setter appelle capitalized avant de sauvegarder. Désormais, toute propriété avec l'annotation @Capitalized stockera uniquement du texte correctement formaté. Cette approche élimine complètement la duplication du code de validation et de formatage.

La projection (projectedValue) — un canal de communication supplémentaire accessible via le préfixe $. Dans SwiftUI, cette fonctionnalité est utilisée partout : $state fournit Binding, et $observedObject retourne Published.Publisher. La projection permet au wrapper de fournir une interface étendue sans modifier l'accès de base à la valeur.

Principaux Property Wrappers dans SwiftUI

SwiftUI inclut cinq Property Wrappers intégrés pour la gestion d'état : @State, @Binding, @ObservedObject, @StateObject et @Environment. Chacun résout une tâche spécifique et est utilisé dans différents scénarios. @State est conçu pour les données locales simples, @Binding — pour passer une référence aux données aux vues enfants, @ObservedObject et @StateObject — pour les objets complexes, @Environment — pour les valeurs système de la hiérarchie.

WrapperObjectifPossession
@StateÉtat local d'une seule vueVue actuelle
@BindingConnexion bidirectionnelle avec le parentVue parente
@ObservedObjectObservation d'un objet externePropriétaire externe
@StateObjectCréation d'un ObservableObjectVue actuelle
@EnvironmentValeurs système de la hiérarchieEnvironnement SwiftUI

Le choix d'un Property Wrapper spécifique dépend de la source de données et de son cycle de vie. Si les données appartiennent à une seule vue et ne sont pas nécessaires aux composants enfants — utilisez @State. Si une vue enfant doit modifier les données du parent — utilisez @Binding. Pour les objets utilisés dans plusieurs vues, @ObservedObject et @StateObject sont appropriés.

@State : état local de la vue

@State est un Property Wrapper pour stocker l'état local à l'intérieur d'une seule vue. SwiftUI gère automatiquement la mémoire pour les propriétés @State et redessine la vue à chaque modification. @State convient aux types simples (String, Int, Bool, enum) et aux structures qui appartiennent exclusivement à la vue actuelle. Lorsque la valeur change, SwiftUI réexécute la propriété body.

swift
struct CounterView: View {
    @State private var count: Int = 0

    var body: some View {
        VStack {
            Text("Compte : \(count)")
            Button("Incrémenter") {
                count += 1
            }
        }
    }
}

Dans l'exemple, la propriété @State count stocke la valeur actuelle du compteur. SwiftUI crée une zone de stockage pour cette propriété sur le tas et la lie au cycle de vie de CounterView. Lorsque le bouton est pressé, count augmente de 1, SwiftUI détecte le changement et réexécute body, affichant la nouvelle valeur. Important : @State ne doit pas être utilisé pour les types de référence complexes — @StateObject et @ObservedObject sont conçus pour cela.

@Binding : connexion bidirectionnelle entre vues

@Binding crée une référence vers une source de données appartenant à une autre vue. Binding ne stocke pas de valeur par lui-même — il lit et écrit des données via @State, @StateObject ou un autre Binding passé du parent. Cela permet aux composants enfants de modifier l'état de l'ancêtre sans posséder directement les données et sans callbacks.

swift
struct ToggleSwitch: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

Dans le listing, ToggleSwitch reçoit @BindingBool de la vue parente. Le parent crée @State var isToggleOn = false et passe $isToggleOn à l'initialiseur de ToggleSwitch. Lorsque l'utilisateur bascule l'interrupteur dans la vue enfant, le changement est immédiatement reflété dans le @State du parent. Le mécanisme Binding élimine complètement le besoin de délégués ou de closures pour remonter les changements dans la hiérarchie.

@ObservedObject et @StateObject : données externes

@ObservedObject est un Property Wrapper pour observer une instance d'ObservableObject passée de l'extérieur. La vue ne possède pas cet objet — il est créé dans le composant parent ou injecté via Environment. Lorsqu'une propriété @Published dans l'ObservableObject change, SwiftUI redessine toutes les vues abonnées via @ObservedObject.

@StateObject — un wrapper pour créer et posséder un ObservableObject directement dans la vue. Contrairement à @ObservedObject, @StateObject garantit une instance unique de l'objet pendant tout le cycle de vie de la vue. Même si SwiftUI recrée la structure de la vue (ce qui arrive fréquemment), @StateObject préserve l'objet existant et n'appelle pas l'initialiseur à nouveau.

swift
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

struct ProfileView: View {
    @StateObject var settings = UserSettings()

    var body: some View {
        ChildProfileView(settings: settings)
    }
}

struct ChildProfileView: View {
    @ObservedObject var settings: UserSettings

    var body: some View {
        Text("Bonjour, \(settings.username)")
    }
}

Dans l'exemple, ProfileView crée UserSettings via @StateObject, devenant propriétaire de l'objet. ChildProfileView reçoit la même instance via @ObservedObject — observe mais ne gère pas le cycle de vie. Lorsque username change, les deux vues sont mises à jour. Si ChildProfileView utilisait @StateObject au lieu de @ObservedObject, une nouvelle instance avec la valeur initiale serait créée à chaque rendu.

La règle clé : @StateObject est utilisé dans la vue qui crée l'objet (source de vérité), tandis que @ObservedObject est utilisé dans la vue qui reçoit un objet déjà créé du parent. Violer cette règle entraîne une perte d'état ou des recréations inattendues de données.

Création d'un Property Wrapper personnalisé

Swift permet de créer des Property Wrappers personnalisés pour toute logique répétitive d'accès aux propriétés. Il suffit de déclarer une structure ou une classe avec l'attribut @propertyWrapper et d'implémenter wrappedValue. Ci-dessous est montré le wrapper UserDefaultsWrapper, qui synchronise automatiquement la valeur avec UserDefaults.

swift
@propertyWrapper
struct UserDefaultsWrapper<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

struct AppConfig {
    @UserDefaultsWrapper(key: "theme", defaultValue: "light")
    var theme: String
}

Le wrapper UserDefaultsWrapper utilise un générique T pour fonctionner avec tout type de données supporté par UserDefaults. Le getter lit la valeur par clé, le setter l'écrit. Appliquer @UserDefaultsWrapper(key:defaultValue:) à la propriété theme la lie automatiquement au stockage — toute la logique UserDefaults est cachée à l'intérieur du wrapper. C'est un exemple typique de réduction de code boilerplate avec les Property Wrappers.

Lors de la création de wrappers personnalisés, il est important de considérer les performances. Étant donné que le getter et le setter sont appelés à chaque accès à la propriété, les opérations lourdes d'E/S ne doivent pas être placées dans wrappedValue. Pour le stockage de données asynchrone, il est préférable de combiner les Property Wrappers avec ObservableObject et @Published.

Foire aux questions

Quelle est la différence entre @State et @StateObject ?

@State est conçu pour les types simples (String, Int, Bool) et les structures, tandis que @StateObject est pour les types de référence implémentant ObservableObject. @State stocke la valeur directement dans SwiftUI, @StateObject gère une instance de classe sur le tas.

Peut-on utiliser @Binding sans @State ?

Oui, @Binding peut être créé à partir de @StateObject, @ObservedObject ou d'un autre Binding via la projection $. Binding peut également être initialisé depuis ObservableObject via $object.$publishedProperty ou depuis InlineBinding via Binding.constant(value).

Quel Property Wrapper choisir pour des données utilisées dans toute l'application ?

Pour les données globales, utilisez @EnvironmentObject ou injectez ObservableObject via EnvironmentValues. @StateObject convient à la vue racine avec une transmission ultérieure via @ObservedObject aux composants enfants.

Pourquoi @ObservedObject peut-il recréer des données ?

@ObservedObject ne possède pas l'objet — si la vue parente est recréée et passe une nouvelle instance, @ObservedObject basculera vers celle-ci. Pour éviter la perte d'état, la vue propriétaire doit utiliser @StateObject.

Peut-on créer un Property Wrapper pour des opérations asynchrones ?

Oui, mais il est plus facile d'utiliser une combinaison d'ObservableObject avec @Published et des fonctions asynchrones à l'intérieur de la classe. Property Wrapper est synchrone par nature — wrappedValue est calculé à chaque accès, ce qui ne convient pas aux opérations de longue durée.

Résumé

  • Property Wrapper — construction syntaxique de Swift 5.1 pour encapsuler la logique d'accès aux propriétés sans duplication de code
  • @State — état local pour les types simples, redessine automatiquement la vue lors du changement
  • @Binding — référence aux données de la vue parente sans possession, accessible via la projection $
  • @StateObject — possession d'ObservableObject avec garantie d'instance unique par cycle de vie
  • @ObservedObject — observation d'ObservableObject passé du composant parent
  • @Environment — accès aux objets d'environnement système de SwiftUI
  • Wrappers personnalisés sont créés via l'attribut @propertyWrapper et l'implémentation de wrappedValue

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.

Discuter du projet

Lisez aussi