Property Wrapper is een Swift-mechanisme dat een logicalaag toevoegt aan de toegang en wijziging van eigenschappen zonder code-duplicatie. In SwiftUI zijn Property Wrappers de basis geworden van het beheren van de status van weergaven: @State, @Binding, @ObservedObject, @StateObject en @Environment. Volgens de Swift-documentatie (2025) verminderen property wrappers de boilerplate-code in projecten gemiddeld met 40%. Begrip van Property Wrapper is noodzakelijk voor elke iOS-ontwikkelaar om effectief met het framework te werken.
Belangrijkste punten
Property Wrapper is een taalconstructie in Swift, geïntroduceerd in versie 5.1, die het mogelijk maakt de logica voor toegang tot een eigenschap in een apart type te inkapselen. In plaats van herhalende getters en setters in elke klasse te schrijven, declareert de ontwikkelaar de wrapper eenmalig en past deze toe via de @-annotatie voor het type. Swift wikkelt de eigenschap automatisch in het opgegeven type en roept de methoden wrappedValue en projectedValue aan bij lezen en schrijven. Volgens Apple (WWDC 2019) zijn Property Wrappers de belangrijkste abstractie voor SwiftUI geworden.
Een property wrapper is een structuur of klasse met het attribuut @propertyWrapper. Intern moet zo'n type de eigenschap wrappedValue implementeren die de huidige waarde retourneert en instelt. De Swift-compiler vervangt verwijzingen naar de oorspronkelijke eigenschap door aanroepen van wrappedValue, waardoor de implementatie volledig verborgen blijft voor de aanroepende code. Daarnaast kan projectedValue worden gedefinieerd — een projectie die toegankelijk is via het $-symbool.
Het voordeel van Property Wrappers ligt in het hergebruik van logica. Zo kan men een wrapper maken voor e-mailvalidatie, het cachen van waarden of synchronisatie met opslag — en deze toepassen op elke eigenschap in het project. In SwiftUI wordt dit concept overal gebruikt: elk mechanisme voor statusbeheer is geïmplementeerd als een afzonderlijke Property Wrapper.
Bij het declareren van een eigenschap met de annotatie @WrapperType var value: T transformeert de Swift-compiler de code. Hij maakt een instantie van WrapperType en genereert toegang tot de eigenschap via wrappedValue. De broncode let x = value wordt let x = _value.wrappedValue, en value = newValue wordt _value.wrappedValue = newValue. Deze transformatie vindt plaats tijdens het compileren, zonder runtime-overhead.
@propertyWrapper
struct Capitalized {
private var text: String
var wrappedValue: String {
get { text }
set { text = newValue.capitalized }
}
init(initialValue: String) {
text = initialValue.capitalized
}
}
In de listing wordt de wrapper Capitalized getoond, die tekst automatisch met een hoofdletter formatteert. Bij het toewijzen van een waarde roept de setter capitalized aan vóór het opslaan. Nu zal elke eigenschap met de annotatie @Capitalized alleen correct geformatteerde tekst opslaan. Deze aanpak elimineert volledig de duplicatie van validatie- en opmaakcode.
Projectie (projectedValue) — een extra communicatiekanaal, toegankelijk via het voorvoegsel $. In SwiftUI wordt deze mogelijkheid overal gebruikt: $state geeft Binding
SwiftUI bevat vijf ingebouwde Property Wrappers voor statusbeheer: @State, @Binding, @ObservedObject, @StateObject en @Environment. Elk ervan lost een specifieke taak op en wordt in verschillende scenario's gebruikt. @State is bedoeld voor eenvoudige lokale gegevens, @Binding — voor het doorgeven van een verwijzing naar gegevens in onderliggende weergaven, @ObservedObject en @StateObject — voor complexe objecten, @Environment — voor systeemwaarden uit de hiërarchie.
| Wrapper | Doel | Eigendom |
|---|---|---|
| @State | Lokale status van één weergave | Huidige weergave |
| @Binding | Tweerichtingsverbinding met ouder | Bovenliggende weergave |
| @ObservedObject | Observatie van extern object | Externe eigenaar |
| @StateObject | Aanmaken ObservableObject | Huidige weergave |
| @Environment | Systeemwaarden uit hiërarchie | SwiftUI-omgeving |
De keuze voor een specifieke Property Wrapper hangt af van de gegevensbron en hun levenscyclus. Als de gegevens bij één weergave horen en niet nodig zijn voor onderliggende componenten — gebruik @State. Als een onderliggende weergave de gegevens van de ouder moet wijzigen — pas @Binding toe. Voor objecten die in meerdere weergaven worden gebruikt, zijn @ObservedObject en @StateObject geschikt.
@State is een Property Wrapper voor het opslaan van lokale status binnen één weergave. SwiftUI beheert automatisch het geheugen voor @State-eigenschappen en hertekent de weergave bij elke wijziging. @State is geschikt voor eenvoudige typen (String, Int, Bool, enum) en structuren die uitsluitend bij de huidige weergave horen. Bij wijziging van de waarde herstart SwiftUI de body-eigenschap.
struct CounterView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("Aantal: \(count)")
Button("Verhogen") {
count += 1
}
}
}
}
In het voorbeeld slaat de @State-eigenschap count de huidige tellerwaarde op. SwiftUI creëert een opslagruimte voor deze eigenschap in de heap en koppelt deze aan de levenscyclus van CounterView. Bij het indrukken van de knop wordt count met 1 verhoogd, SwiftUI detecteert de wijziging en herstart body, waarbij de nieuwe waarde wordt weergegeven. Belangrijk: @State moet niet worden gebruikt voor complexe referentietypen — daarvoor zijn @StateObject en @ObservedObject bedoeld.
@Binding maakt een verwijzing naar een gegevensbron die bij een andere weergave hoort. Binding slaat de waarde niet zelfstandig op — hij leest en schrijft gegevens via @State, @StateObject of een andere Binding die van de ouder is doorgegeven. Dit stelt onderliggende componenten in staat de status van de voorouder te wijzigen zonder direct eigendom van de gegevens en zonder callbacks.
struct ToggleSwitch: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Switch", isOn: $isOn)
}
}
In de listing ontvangt ToggleSwitch een @BindingBool van de bovenliggende weergave. De ouder maakt @State var isToggleOn = false en geeft $isToggleOn door aan de initializer van ToggleSwitch. Wanneer de gebruiker Toggle in de onderliggende weergave omschakelt, wordt de wijziging onmiddellijk weerspiegeld in de @State van de ouder. Het Binding-mechanisme elimineert volledig de behoefte aan delegaten of closures voor het doorgeven van wijzigingen omhoog in de hiërarchie.
@ObservedObject is een Property Wrapper voor het observeren van een ObservableObject-instantie die extern is doorgegeven. De weergave is geen eigenaar van dit object — het wordt gemaakt in de bovenliggende component of geïnjecteerd via Environment. Wanneer een @Published-eigenschap binnen ObservableObject verandert, hertekent SwiftUI alle weergaven die via @ObservedObject zijn geabonneerd.
@StateObject is een wrapper voor het maken en eigendom van ObservableObject direct in de weergave. In tegenstelling tot @ObservedObject garandeert @StateObject een enkele instantie van het object gedurende de volledige levenscyclus van de weergave. Zelfs als SwiftUI de weergavestructuur opnieuw aanmaakt (wat vaak gebeurt), behoudt @StateObject het bestaande object en roept de initializer niet opnieuw aan.
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("Hallo, \(settings.username)")
}
}
In het voorbeeld maakt ProfileView UserSettings aan via @StateObject en wordt eigenaar van het object. ChildProfileView ontvangt dezelfde instantie via @ObservedObject — observeert, maar beheert de levenscyclus niet. Bij wijziging van username worden beide weergaven bijgewerkt. Als ChildProfileView @StateObject in plaats van @ObservedObject had gebruikt, zou bij elke render een nieuwe instantie met de beginwaarde worden gemaakt.
De belangrijkste regel: @StateObject wordt toegepast in de weergave die het object maakt (de bron van waarheid), en @ObservedObject in de weergave die het kant-en-klare object van de ouder ontvangt. Overtreding van deze regel leidt tot verlies van status of onverwacht opnieuw aanmaken van gegevens.
Swift maakt het mogelijk aangepaste Property Wrappers te maken voor elke herhalende logica voor toegang tot eigenschappen. Het volstaat een structuur of klasse met het attribuut @propertyWrapper te declareren en wrappedValue te implementeren. Hieronder wordt de wrapper UserDefaultsWrapper getoond, die de waarde automatisch met UserDefaults synchroniseert.
@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
}
De wrapper UserDefaultsWrapper gebruikt een generic T om met elk door UserDefaults ondersteund gegevenstype te werken. De getter leest de waarde op basis van een sleutel, de setter schrijft deze. Het toepassen van @UserDefaultsWrapper(key:defaultValue:) op de eigenschap theme koppelt deze automatisch aan de opslag — alle logica voor werken met UserDefaults is verborgen in de wrapper. Dit is een typisch voorbeeld van het verminderen van boilerplate-code met Property Wrappers.
Bij het maken van eigen wrappers is het belangrijk rekening te houden met prestaties. Omdat de getter en setter bij elke toegang tot de eigenschap worden aangeroepen, moeten er geen zware I/O-bewerkingen in wrappedValue worden geplaatst. Voor asynchrone gegevensopslag is het beter Property Wrappers te combineren met ObservableObject en @Published.
Veelgestelde vragen
@State is bedoeld voor eenvoudige typen (String, Int, Bool) en structuren, terwijl @StateObject voor referentietypen is die ObservableObject implementeren. @State slaat de waarde direct op in SwiftUI, @StateObject beheert een klasse-instantie in de heap.
Ja, @Binding kan worden gemaakt van @StateObject, @ObservedObject of van een andere Binding met behulp van de $-projectie. Binding wordt ook geïnitialiseerd vanuit ObservableObject via $object.$publishedProperty of vanuit InlineBinding via Binding.constant(value).
Voor globale gegevens gebruikt u @EnvironmentObject of injecteert u ObservableObject via EnvironmentValues. @StateObject is geschikt voor de root-weergave met latere doorgifte via @ObservedObject aan onderliggende componenten.
@ObservedObject is geen eigenaar van het object — als de bovenliggende weergave opnieuw wordt aangemaakt en een nieuwe instantie doorgeeft, schakelt @ObservedObject hiernaar over. Om verlies van status te voorkomen, moet de eigenaar-weergave @StateObject gebruiken.
Ja, maar het is eenvoudiger een combinatie van ObservableObject met @Published en async-functies binnen de klasse te gebruiken. Property Wrapper is van nature synchroon — wrappedValue wordt bij elke toegang berekend, wat niet geschikt is voor langdurige bewerkingen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook