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.