Property Wrapper је Swift механизам који додаје слој логике приступу и измени својстава без дуплирања кода. У SwiftUI-ју, Property Wrapper-и су постали основа управљања стањем приказа: @State, @Binding, @ObservedObject, @StateObject и @Environment. Према Swift документацији (2025), обмоти својстава смањују шаблонски код у пројектима у просеку за 40%. Разумевање Property Wrapper је неопходно сваком iOS програмеру за ефикасан рад са фрејмворком.
Главно
Property Wrapper је језичка конструкција Swift-а, представљена у верзији 5.1, која омогућава инкапсулацију логике приступа својству у засебан тип. Уместо писања понављајућих гетера и сетера у свакој класи, програмер декларише обмоту једном и примењује је кроз анотацију @ испред типа. Swift аутоматски обмоти својство у наведени тип, позивајући његове методе wrappedValue и projectedValue при читању и писању. Према Apple-у (WWDC 2019), Property Wrapper-и су постали кључна апстракција за SwiftUI.
Обмота својства представља структуру или класу са атрибутом @propertyWrapper. Унутра, такав тип мора да имплементира својство wrappedValue које враћа и поставља тренутну вредност. Swift компајлер замењује референце на оригинално својство позивима wrappedValue, потпуно скривајући имплементацију од позивајућег кода. Додатно се може дефинисати projectedValue — пројекција доступна кроз симбол $.
Предност Property Wrapper-а лежи у вишеструком коришћењу логике. На пример, може се креирати обмота за валидацију имејла, кеширање вредности или синхронизацију са складиштем — и применити на било које својство у пројекту. У SwiftUI-ју, овај концепт се користи свуда: сваки механизам управљања стањем је имплементиран као засебан Property Wrapper.
При декларацији својства са анотацијом @WrapperType var value: T, Swift компајлер трансформише код. Он креира инстанцу WrapperType-а и генерише приступ својству кроз wrappedValue. Изворни код let x = value претвара се у let x = _value.wrappedValue, а value = newValue у _value.wrappedValue = newValue. Ова трансформација се дешава у фази компилације, без runtime overhead-а.
@propertyWrapper
struct Capitalized {
private var text: String
var wrappedValue: String {
get { text }
set { text = newValue.capitalized }
}
init(initialValue: String) {
text = initialValue.capitalized
}
}
У листингу је приказана обмота Capitalized, која аутоматски форматира текст са великим словом. При додели вредности, сетер позива capitalized пре чувања. Сада ће свако својство са анотацијом @Capitalized чувати само коректно форматиран текст. Овај приступ потпуно елиминише дуплирање кода валидације и форматирања.
Пројекција (projectedValue) — додатни комуникациони канал, доступан кроз префикс $. У SwiftUI-ју, ова могућност се користи свуда: $state даје Binding
SwiftUI укључује пет уграђених Property Wrapper-а за управљање стањем: @State, @Binding, @ObservedObject, @StateObject и @Environment. Сваки од њих решава конкретан задатак и користи се у различитим сценаријима. @State је намењен једноставним локалним подацима, @Binding — за пренос референце на податке у подређене приказе, @ObservedObject и @StateObject — за сложене објекте, @Environment — за системске вредности из хијерархије.
| Wrapper | Намена | Власништво |
|---|---|---|
| @State | Локално стање једног приказа | Тренутни приказ |
| @Binding | Двосмерна веза са родитељем | Родитељски приказ |
| @ObservedObject | Посматрање спољашњег објекта | Спољашњи власник |
| @StateObject | Креирање ObservableObject-а | Тренутни приказ |
| @Environment | Системске вредности из хијерархије | SwiftUI окружење |
Избор конкретног Property Wrapper-а зависи од извора података и њиховог животног циклуса. Ако подаци припадају једном приказу и нису потребни подређеним компонентама — користите @State. Ако подређени приказ треба да мења податке родитеља — примените @Binding. За објекте који се користе у више приказа погодни су @ObservedObject и @StateObject.
@State је Property Wrapper за чување локалног стања унутар једног приказа. SwiftUI аутоматски управља меморијом за @State својства и прецртава приказ при свакој промени. @State је погодан за једноставне типове (String, Int, Bool, enum) и структуре које припадају искључиво тренутном приказу. При промени вредности, SwiftUI поново покреће својство body.
struct CounterView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("Број: \(count)")
Button("Повећај") {
count += 1
}
}
}
}
У примеру, @State својство count чува тренутну вредност бројача. SwiftUI креира област за складиштење за ово својство на хепу и повезује је са животним циклусом CounterView-а. При притиску на дугме, count се повећава за 1, SwiftUI открива промену и поново покреће body, приказујући нову вредност. Важно: @State не треба користити за сложене референтне типове — за то су намењени @StateObject и @ObservedObject.
@Binding креира референцу на извор података који припада другом приказу. Binding не чува вредност самостално — он чита и пише податке кроз @State, @StateObject или други Binding прослеђен од родитеља. Ово омогућава подређеним компонентама да мењају стање претка без директног власништва над подацима и без callback-ова.
struct ToggleSwitch: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Switch", isOn: $isOn)
}
}
У листингу, ToggleSwitch прима @BindingBool од родитељског приказа. Родитељ креира @State var isToggleOn = false и прослеђује $isToggleOn иницијализатору ToggleSwitch-а. Када корисник пребаци Toggle унутар подређеног приказа, промена се одмах одражава у родитељском @State-у. Механизам Binding потпуно елиминише потребу за делегатима или затварањима за пренос промена навише по хијерархији.
@ObservedObject је Property Wrapper за посматрање инстанце ObservableObject-а прослеђене споља. Приказ не поседује овај објекат — он се креира у родитељској компоненти или убризгава кроз Environment. Када се било које @Published својство унутар ObservableObject-а промени, SwiftUI прецртава све приказе претплаћене кроз @ObservedObject.
@StateObject је обмота за креирање и власништво ObservableObject-а директно у приказу. За разлику од @ObservedObject-а, @StateObject гарантује јединствену инстанцу објекта током целог животног циклуса приказа. Чак и ако SwiftUI поново креира структуру приказа (што се често дешава), @StateObject задржава постојећи објекат и не позива поново иницијализатор.
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("Здраво, \(settings.username)")
}
}
У примеру, ProfileView креира UserSettings кроз @StateObject, постајући власник објекта. ChildProfileView прима исту инстанцу кроз @ObservedObject — посматра, али не управља животним циклусом. При промени username-а, оба приказа се ажурирају. Да је ChildProfileView користио @StateObject уместо @ObservedObject-а, при сваком рендеру би се креирала нова инстанца са почетном вредношћу.
Кључно правило: @StateObject се примењује у приказу који креира објекат (извор истине), а @ObservedObject — у приказу који прима готов објекат од родитеља. Кршење овог правила води ка губитку стања или неочекиваном поновном креирању података.
Swift омогућава креирање прилагођених Property Wrapper-а за било коју понављајућу логику приступа својствима. Довољно је декларисати структуру или класу са атрибутом @propertyWrapper и имплементирати wrappedValue. Испод је приказана обмота UserDefaultsWrapper која аутоматски синхронизује вредност са 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
}
Обмота UserDefaultsWrapper користи generic T за рад са било којим типом података подржаним од стране UserDefaults-а. Getter чита вредност по кључу, setter је уписује. Примена @UserDefaultsWrapper(key:defaultValue:) на својство theme аутоматски га повезује са складиштем — сва логика рада са UserDefaults-ом је скривена унутар обмоте. Ово је типичан пример смањења шаблонског кода помоћу Property Wrapper-а.
При креирању сопствених обмота важно је узети у обзир перформансе. Пошто се гетер и сетер позивају при сваком приступу својству, не треба стављати тешке I/O операције у wrappedValue. За асинхроно складиштење података боље је комбиновати Property Wrapper-е са ObservableObject-ом и @Published-ом.
Често постављана питања
@State је намењен једноставним типовима (String, Int, Bool) и структурама, а @StateObject — референтним типовима који имплементирају ObservableObject. @State чува вредност директно у SwiftUI-ју, @StateObject управља инстанцом класе на хепу.
Да, @Binding се може креирати из @StateObject-а, @ObservedObject-а или другог Binding-а помоћу пројекције $. Такође, Binding се иницијализује из ObservableObject-а кроз $object.$publishedProperty или из InlineBinding-а кроз Binding.constant(value).
За глобалне податке користите @EnvironmentObject или убризгајте ObservableObject кроз EnvironmentValues. @StateObject је погодан за коренски приказ са накнадним преносом кроз @ObservedObject до подређених компоненти.
@ObservedObject не поседује објекат — ако се родитељски приказ поново креира и проследи нову инстанцу, @ObservedObject ће се пребацити на њу. Да бисте избегли губитак стања, власнички приказ треба да користи @StateObject.
Да, али једноставније је користити комбинацију ObservableObject са @Published и async функцијама унутар класе. Property Wrapper је синхрон по својој природи — wrappedValue се израчунава при сваком приступу, што није погодно за дуготрајне операције.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође