Property Wrapper je mechanismus Swift, který přidává vrstvu logiky k přístupu a změně vlastností bez duplicitního kódu. Ve SwiftUI se Property Wrappers staly základem správy stavu zobrazení: @State, @Binding, @ObservedObject, @StateObject a @Environment. Podle dokumentace Swift (2025) snižují obaly vlastností boilerplate kód v projektech v průměru o 40%. Porozumění Property Wrapper je nezbytné pro každého iOS vývojáře pro efektivní práci s frameworkem.
Hlavní body
Property Wrapper je jazyková konstrukce Swift, představená ve verzi 5.1, která umožňuje zapouzdřit logiku přístupu k vlastnosti do samostatného typu. Místo psaní opakujících se getterů a setterů v každé třídě vývojář deklaruje obal jednou a aplikuje ho pomocí anotace @ před typem. Swift automaticky obalí vlastnost do zadaného typu a při čtení a zápisu volá jeho metody wrappedValue a projectedValue. Podle Apple (WWDC 2019) se Property Wrappers staly klíčovou abstrakcí pro SwiftUI.
Obal vlastnosti je struktura nebo třída s atributem @propertyWrapper. Uvnitř takový typ musí implementovat vlastnost wrappedValue, která vrací a nastavuje aktuální hodnotu. Překladač Swift nahrazuje odkazy na původní vlastnost voláními wrappedValue, čímž zcela skrývá implementaci před volajícím kódem. Dodatečně lze definovat projectedValue — projekci přístupnou přes symbol $.
Výhoda Property Wrappers spočívá v opětovném použití logiky. Například lze vytvořit obal pro validaci e-mailu, ukládání hodnot do mezipaměti nebo synchronizaci s úložištěm — a aplikovat ho na libovolnou vlastnost v projektu. Ve SwiftUI je tento koncept používán všude: každý mechanismus správy stavu je implementován jako samostatný Property Wrapper.
Při deklaraci vlastnosti s anotací @WrapperType var value: T překladač Swift transformuje kód. Vytvoří instanci WrapperType a generuje přístup k vlastnosti přes wrappedValue. Zdrojový kód let x = value se změní na let x = _value.wrappedValue a value = newValue na _value.wrappedValue = newValue. Tato transformace probíhá ve fázi překladu bez runtime režie.
@propertyWrapper
struct Capitalized {
private var text: String
var wrappedValue: String {
get { text }
set { text = newValue.capitalized }
}
init(initialValue: String) {
text = initialValue.capitalized
}
}
Ve výpisu je zobrazen obal Capitalized, který automaticky formátuje text s velkým písmenem. Při přiřazení hodnoty setter zavolá capitalized před uložením. Nyní bude jakákoli vlastnost s anotací @Capitalized ukládat pouze správně naformátovaný text. Tento přístup zcela eliminuje duplicitu validačního a formátovacího kódu.
Projekce (projectedValue) — dodatečný komunikační kanál přístupný přes prefix $. Ve SwiftUI je tato možnost používána všude: $state poskytuje Binding
SwiftUI obsahuje pět vestavěných Property Wrappers pro správu stavu: @State, @Binding, @ObservedObject, @StateObject a @Environment. Každý z nich řeší konkrétní úkol a používá se v různých scénářích. @State je určen pro jednoduchá lokální data, @Binding — pro předávání reference na data do podřízených zobrazení, @ObservedObject a @StateObject — pro složité objekty, @Environment — pro systémové hodnoty z hierarchie.
| Wrapper | Účel | Vlastnictví |
|---|---|---|
| @State | Lokální stav jednoho zobrazení | Aktuální zobrazení |
| @Binding | Obousměrné propojení s rodičem | Nadřazené zobrazení |
| @ObservedObject | Pozorování externího objektu | Externí vlastník |
| @StateObject | Vytvoření ObservableObject | Aktuální zobrazení |
| @Environment | Systémové hodnoty z hierarchie | Prostředí SwiftUI |
Výběr konkrétního Property Wrapper závisí na zdroji dat a jejich životním cyklu. Pokud data patří jednomu zobrazení a nejsou potřeba pro podřízené komponenty — použijte @State. Pokud podřízené zobrazení potřebuje měnit data rodiče — aplikujte @Binding. Pro objekty používané ve více zobrazeních jsou vhodné @ObservedObject a @StateObject.
@State je Property Wrapper pro ukládání lokálního stavu uvnitř jednoho zobrazení. SwiftUI automaticky spravuje paměť pro @State vlastnosti a překresluje zobrazení při každé změně. @State je vhodný pro jednoduché typy (String, Int, Bool, enum) a struktury, které patří výhradně aktuálnímu zobrazení. Při změně hodnoty SwiftUI znovu spustí vlastnost body.
struct CounterView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("Počet: \(count)")
Button("Zvýšit") {
count += 1
}
}
}
}
V příkladu vlastnost @State count ukládá aktuální hodnotu počítadla. SwiftUI vytváří úložný prostor pro tuto vlastnost na haldě a propojuje ho s životním cyklem CounterView. Při stisknutí tlačítka se count zvýší o 1, SwiftUI detekuje změnu a znovu spustí body, čímž zobrazí novou hodnotu. Důležité: @State by se neměl používat pro složité referenční typy — k tomu slouží @StateObject a @ObservedObject.
@Binding vytváří referenci na zdroj dat patřící jinému zobrazení. Binding neukládá hodnotu samostatně — čte a zapisuje data přes @State, @StateObject nebo jiný Binding předaný od rodiče. To umožňuje podřízeným komponentám měnit stav předka bez přímého vlastnictví dat a bez callbacků.
struct ToggleSwitch: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Switch", isOn: $isOn)
}
}
Ve výpisu ToggleSwitch přijímá @BindingBool od nadřazeného zobrazení. Rodič vytvoří @State var isToggleOn = false a předá $isToggleOn inicializátoru ToggleSwitch. Když uživatel přepne Toggle uvnitř podřízeného zobrazení, změna se okamžitě projeví v rodičovském @State. Mechanismus Binding zcela eliminuje potřebu delegátů nebo closures pro předávání změn nahoru hierarchií.
@ObservedObject je Property Wrapper pro pozorování instance ObservableObject předané zvenčí. Zobrazení není vlastníkem tohoto objektu — je vytvořen v nadřazené komponentě nebo injektován přes Environment. Když se jakákoli @Published vlastnost uvnitř ObservableObject změní, SwiftUI překreslí všechna zobrazení přihlášená přes @ObservedObject.
@StateObject je obal pro vytvoření a vlastnictví ObservableObject přímo v zobrazení. Na rozdíl od @ObservedObject, @StateObject garantuje jedinou instanci objektu po celý životní cyklus zobrazení. I když SwiftUI znovu vytvoří strukturu zobrazení (což se stává často), @StateObject zachová existující objekt a znovu nezavolá inicializátor.
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("Ahoj, \(settings.username)")
}
}
V příkladu ProfileView vytváří UserSettings přes @StateObject a stává se vlastníkem objektu. ChildProfileView přijímá stejnou instanci přes @ObservedObject — pozoruje, ale nespravuje životní cyklus. Při změně username se obě zobrazení aktualizují. Pokud by ChildProfileView použil @StateObject místo @ObservedObject, při každém renderu by se vytvářela nová instance s počáteční hodnotou.
Klíčové pravidlo: @StateObject se aplikuje v zobrazení, které objekt vytváří (zdroj pravdy), a @ObservedObject v zobrazení, které přijímá hotový objekt od rodiče. Porušení tohoto pravidla vede ke ztrátě stavu nebo neočekávanému opětovnému vytváření dat.
Swift umožňuje vytvářet vlastní Property Wrappers pro jakoukoli opakující se logiku přístupu k vlastnostem. Stačí deklarovat strukturu nebo třídu s atributem @propertyWrapper a implementovat wrappedValue. Níže je zobrazen obal UserDefaultsWrapper, který automaticky synchronizuje hodnotu s 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
}
Obal UserDefaultsWrapper používá generic T pro práci s libovolným datovým typem podporovaným UserDefaults. Getter čte hodnotu podle klíče, setter zapisuje. Aplikace @UserDefaultsWrapper(key:defaultValue:) na vlastnost theme ji automaticky propojí s úložištěm — veškerá logika práce s UserDefaults je skryta uvnitř obalu. To je typický příklad snížení boilerplate kódu pomocí Property Wrappers.
Při vytváření vlastních obalů je důležité zohlednit výkon. Protože getter a setter jsou volány při každém přístupu k vlastnosti, neměly by být do wrappedValue umísťovány těžké I/O operace. Pro asynchronní ukládání dat je lepší kombinovat Property Wrappers s ObservableObject a @Published.
Často kladené otázky
@State je určen pro jednoduché typy (String, Int, Bool) a struktury, zatímco @StateObject pro referenční typy implementující ObservableObject. @State ukládá hodnotu přímo ve SwiftUI, @StateObject spravuje instanci třídy na haldě.
Ano, @Binding lze vytvořit z @StateObject, @ObservedObject nebo z jiného Binding pomocí projekce $. Binding se také inicializuje z ObservableObject přes $object.$publishedProperty nebo z InlineBinding přes Binding.constant(value).
Pro globální data použijte @EnvironmentObject nebo injektujte ObservableObject přes EnvironmentValues. @StateObject je vhodný pro kořenové zobrazení s následným předáváním přes @ObservedObject podřízeným komponentám.
@ObservedObject není vlastníkem objektu — pokud je nadřazené zobrazení znovu vytvořeno a předá novou instanci, @ObservedObject na ni přepne. Aby se předešlo ztrátě stavu, mělo by vlastnící zobrazení použít @StateObject.
Ano, ale jednodušší je použít kombinaci ObservableObject s @Published a async funkcemi uvnitř třídy. Property Wrapper je ze své podstaty synchronní — wrappedValue se vypočítává při každém přístupu, což není vhodné pro dlouhodobé operace.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také