Property Wrapper är en Swift-mekanism som lägger till ett logiklager till åtkomst och ändring av egenskaper utan kodduplicering. I SwiftUI har Property Wrappers blivit grunden för hantering av vyns tillstånd: @State, @Binding, @ObservedObject, @StateObject och @Environment. Enligt Swift-dokumentationen (2025) minskar egenskapsomslag boilerplate-koden i projekt med i genomsnitt 40%. Förståelse av Property Wrapper är nödvändigt för varje iOS-utvecklare för att arbeta effektivt med ramverket.
Huvudpunkter
Property Wrapper är en språkkonstruktion i Swift som introducerades i version 5.1 och som gör det möjligt att kapsla in logiken för åtkomst till en egenskap i en separat typ. Istället för att skriva upprepade getters och setters i varje klass deklarerar utvecklaren omslaget en gång och applicerar det via @-annotationen före typen. Swift slår automatiskt in egenskapen i den angivna typen och anropar dess metoder wrappedValue och projectedValue vid läsning och skrivning. Enligt Apple (WWDC 2019) har Property Wrappers blivit den viktigaste abstraktionen för SwiftUI.
Ett egenskapsomslag är en struktur eller klass med attributet @propertyWrapper. Internt måste en sådan typ implementera egenskapen wrappedValue som returnerar och ställer in det aktuella värdet. Swift-kompilatorn ersätter referenser till den ursprungliga egenskapen med anrop till wrappedValue, vilket helt döljer implementeringen från den anropande koden. Dessutom kan projectedValue definieras — en projektion som är tillgänglig via $-symbolen.
Fördelen med Property Wrappers ligger i återanvändning av logik. Till exempel kan man skapa ett omslag för e-postvalidering, cachning av värden eller synkronisering med lagring — och applicera det på vilken egenskap som helst i projektet. I SwiftUI används detta koncept överallt: varje mekanism för tillståndshantering är implementerad som en separat Property Wrapper.
Vid deklaration av en egenskap med annotationen @WrapperType var value: T transformerar Swift-kompilatorn koden. Den skapar en instans av WrapperType och genererar åtkomst till egenskapen via wrappedValue. Källkoden let x = value blir let x = _value.wrappedValue, och value = newValue blir _value.wrappedValue = newValue. Denna transformation sker vid kompilering utan runtime-overhead.
@propertyWrapper
struct Capitalized {
private var text: String
var wrappedValue: String {
get { text }
set { text = newValue.capitalized }
}
init(initialValue: String) {
text = initialValue.capitalized
}
}
I listningen visas omslaget Capitalized, som automatiskt formaterar text med versal. Vid tilldelning av värde anropar settern capitalized före lagring. Nu kommer varje egenskap med annotationen @Capitalized endast att lagra korrekt formaterad text. Detta tillvägagångssätt eliminerar helt duplicering av validerings- och formateringskod.
Projektion (projectedValue) — en extra kommunikationskanal som är tillgänglig via prefixet $. I SwiftUI används denna funktion överallt: $state ger Binding
SwiftUI innehåller fem inbyggda Property Wrappers för tillståndshantering: @State, @Binding, @ObservedObject, @StateObject och @Environment. Var och en löser en specifik uppgift och används i olika scenarier. @State är avsett för enkla lokala data, @Binding — för att skicka en referens till data till underordnade vyer, @ObservedObject och @StateObject — för komplexa objekt, @Environment — för systemvärden från hierarkin.
| Wrapper | Ändamål | Ägarskap |
|---|---|---|
| @State | Lokal status för en vy | Aktuell vy |
| @Binding | Tvåvägsförbindelse med förälder | Överordnad vy |
| @ObservedObject | Observation av externt objekt | Extern ägare |
| @StateObject | Skapa ObservableObject | Aktuell vy |
| @Environment | Systemvärden från hierarkin | SwiftUI-miljö |
Valet av en specifik Property Wrapper beror på datakällan och dess livscykel. Om data tillhör en vy och inte behövs av underordnade komponenter — använd @State. Om en underordnad vy behöver ändra förälderns data — applicera @Binding. För objekt som används i flera vyer är @ObservedObject och @StateObject lämpliga.
@State är en Property Wrapper för att lagra lokal status inom en vy. SwiftUI hanterar automatiskt minnet för @State-egenskaper och ritar om vyn vid varje ändring. @State är lämplig för enkla typer (String, Int, Bool, enum) och strukturer som uteslutande tillhör den aktuella vyn. När värdet ändras kör SwiftUI om body-egenskapen.
struct CounterView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("Antal: \(count)")
Button("Öka") {
count += 1
}
}
}
}
I exemplet lagrar @State-egenskapen count det aktuella värdet för räknaren. SwiftUI skapar ett lagringsområde för denna egenskap på heapen och kopplar det till CounterViews livscykel. När knappen trycks in ökar count med 1, SwiftUI upptäcker ändringen och kör om body, vilket visar det nya värdet. Viktigt: @State bör inte användas för komplexa referenstyper — för det ändamålet är @StateObject och @ObservedObject avsedda.
@Binding skapar en referens till en datakälla som tillhör en annan vy. Binding lagrar inte värdet självständigt — den läser och skriver data via @State, @StateObject eller en annan Binding som skickats från föräldern. Detta tillåter underordnade komponenter att ändra förfaderns status utan direkt ägarskap av data och utan callbacks.
struct ToggleSwitch: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Switch", isOn: $isOn)
}
}
I listningen tar ToggleSwitch emot en @BindingBool från den överordnade vyn. Föräldern skapar @State var isToggleOn = false och skickar $isToggleOn till ToggleSwitches initierare. När användaren växlar Toggle inuti den underordnade vyn reflekteras ändringen omedelbart i förälderns @State. Binding-mekanismen eliminerar helt behovet av delegater eller closures för att skicka ändringar uppåt i hierarkin.
@ObservedObject är en Property Wrapper för att observera en ObservableObject-instans som skickats utifrån. Vyn äger inte detta objekt — det skapas i den överordnade komponenten eller injiceras via Environment. När någon @Published-egenskap inuti ObservableObject ändras, ritar SwiftUI om alla vyer som prenumererar via @ObservedObject.
@StateObject är ett omslag för att skapa och äga ObservableObject direkt i vyn. Till skillnad från @ObservedObject garanterar @StateObject en enda instans av objektet under hela vyns livscykel. Även om SwiftUI återskapar vystrukturen (vilket händer ofta), behåller @StateObject det befintliga objektet och anropar inte initieraren igen.
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("Hej, \(settings.username)")
}
}
I exemplet skapar ProfileView UserSettings via @StateObject och blir ägare av objektet. ChildProfileView tar emot samma instans via @ObservedObject — observerar men hanterar inte livscykeln. När username ändras uppdateras båda vyerna. Om ChildProfileView hade använt @StateObject istället för @ObservedObject skulle en ny instans med startvärdet skapas vid varje rendering.
Nyckelregeln: @StateObject tillämpas i vyn som skapar objektet (sanningens källa), och @ObservedObject i vyn som tar emot det färdiga objektet från föräldern. Brott mot denna regel leder till statusförlust eller oväntad återskapning av data.
Swift tillåter att skapa anpassade Property Wrappers för vilken repetitiv logik som helst för egenskapsåtkomst. Det räcker att deklarera en struktur eller klass med attributet @propertyWrapper och implementera wrappedValue. Nedan visas omslaget UserDefaultsWrapper som automatiskt synkroniserar värdet med UserDefaults.
@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
}
Omslaget UserDefaultsWrapper använder en generisk T för att arbeta med vilken datatyp som helst som stöds av UserDefaults. Gettern läser värdet baserat på en nyckel, settern skriver. Tillämpning av @UserDefaultsWrapper(key:defaultValue:) på egenskapen theme kopplar den automatiskt till lagringen — all logik för arbete med UserDefaults är gömd inuti omslaget. Detta är ett typiskt exempel på att minska boilerplate-kod med Property Wrappers.
När du skapar egna omslag är det viktigt att ta hänsyn till prestanda. Eftersom gettern och settern anropas vid varje åtkomst till egenskapen bör tunga I/O-operationer inte placeras i wrappedValue. För asynkron datalagring är det bättre att kombinera Property Wrappers med ObservableObject och @Published.
Vanliga frågor
@State är avsett för enkla typer (String, Int, Bool) och strukturer, medan @StateObject är för referenstyper som implementerar ObservableObject. @State lagrar värdet direkt i SwiftUI, @StateObject hanterar en klassinstans på heapen.
Ja, @Binding kan skapas från @StateObject, @ObservedObject eller från en annan Binding med hjälp av $-projektionen. Binding initieras också från ObservableObject via $object.$publishedProperty eller från InlineBinding via Binding.constant(value).
För global data, använd @EnvironmentObject eller injicera ObservableObject via EnvironmentValues. @StateObject är lämplig för rotvyn med efterföljande vidarebefordran via @ObservedObject till underordnade komponenter.
@ObservedObject äger inte objektet — om den överordnade vyn återskapas och skickar en ny instans, växlar @ObservedObject till den. För att undvika statusförlust bör den ägande vyn använda @StateObject.
Ja, men det är enklare att använda en kombination av ObservableObject med @Published och async-funktioner inuti klassen. Property Wrapper är synkron till sin natur — wrappedValue beräknas vid varje åtkomst, vilket inte är lämpligt för långvariga operationer.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också