Les Property Wrappers en Swift : guide technique complet
Introduits avec Swift 5.1, les Property Wrappers permettent d’encapsuler une logique réutilisable autour des propriétés. Ils jouent aujourd’hui un rôle central dans SwiftUI, Combine, SwiftData et de nombreuses architectures modernes.
Cet article détaille :
- le fonctionnement interne des Property Wrappers,
- les wrappers fournis par Apple,
- la création de wrappers personnalisés,
- les usages avancés,
- les limites et pièges en production.
1. Qu’est-ce qu’un Property Wrapper ?
Un Property Wrapper est une structure ou classe annotée avec
@propertyWrapper qui permet d’ajouter un comportement automatique
à une propriété.
Il agit comme un intermédiaire entre :
- la valeur stockée,
- la lecture de la propriété,
- l’écriture de la propriété.
Avant les Property Wrappers, on utilisait :
- des getters/setters manuels,
- des classes utilitaires,
- ou de la duplication de logique.
2. Exemple simple sans Property Wrapper
class Settings {
private var _username: String = ""
var username: String {
get { _username }
set {
_username = newValue.trimmingCharacters(in: .whitespaces)
}
}
}
La logique de nettoyage est directement couplée à la propriété.
3. Même exemple avec un Property Wrapper
@propertyWrapper
struct Trimmed {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespaces) }
}
}
class Settings {
@Trimmed var username: String
}
La logique est maintenant :
- réutilisable,
- testable,
- séparée du modèle.
4. Fonctionnement interne
Lors de la compilation, Swift transforme :
@Wrapper var value: Int
En quelque chose de proche de :
private var _value = Wrapper()
var value: Int {
get { _value.wrappedValue }
set { _value.wrappedValue = newValue }
}
Le wrapper est donc une propriété stockée séparée.
5. wrappedValue et projectedValue
5.1 wrappedValue
wrappedValue est la valeur principale exposée par la propriété.
var wrappedValue: Int
5.2 projectedValue ($)
projectedValue expose une valeur secondaire accessible via $.
@propertyWrapper
struct Clamped {
private var value: Int
let range: ClosedRange<Int>
init(wrappedValue: Int, _ range: ClosedRange<Int>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
var projectedValue: Bool {
value == range.lowerBound || value == range.upperBound
}
}
Utilisation :
@Clamped(0...100) var score = 150
print(score) // 100
print($score) // true (borne atteinte)
6. Property Wrappers fournis par Apple
6.1 @State (SwiftUI)
Stocke un état local à une vue SwiftUI.
@State private var counter = 0
- lié au cycle de vie de la vue,
- non partageable directement.
6.2 @Binding
Crée une référence vers un état externe.
@Binding var isOn: Bool
6.3 @ObservedObject / @StateObject
Utilisés avec Combine et ObservableObject.
@StateObject: création et ownership,@ObservedObject: observation externe.
6.4 @Published
Wrapper Combine pour notifier automatiquement les abonnés.
@Published var name: String
6.5 @AppStorage / @SceneStorage
Persistance automatique via UserDefaults ou la scène.
@AppStorage("theme") var theme = "light"
7. Property Wrappers et dépendances
Les wrappers sont souvent utilisés pour :
- l’injection de dépendances,
- le lazy loading,
- la validation automatique.
@propertyWrapper
struct Injected<T> {
var wrappedValue: T {
DependencyContainer.resolve(T.self)
}
}
8. Contraintes et limitations
- Ne peuvent pas être utilisés sur des paramètres de fonction.
- Peuvent compliquer le debug.
- Attention aux effets de bord cachés.
- Les wrappers sont des types ? impact mémoire possible.
9. Bonnes pratiques professionnelles
- Un wrapper = une responsabilité claire.
- Éviter les wrappers trop “magiques”.
- Documenter le comportement.
- Tester le wrapper indépendamment.
- Préférer la clarté à la concision.
Conclusion
Les Property Wrappers sont un pilier du Swift moderne. Bien utilisés, ils permettent :
- un code plus expressif,
- une meilleure séparation des responsabilités,
- une intégration naturelle avec SwiftUI et Combine.
Mal utilisés, ils peuvent rendre le code opaque. Comme tout outil puissant, ils doivent être employés avec parcimonie et compréhension de leur fonctionnement interne.