Property Wrapper: concepte cheie, sintaxă și exemple în SwiftUI

Autor: IT Sectr Publicat: 2026-06-19 Timp de citire: 8 min

Property Wrapper este un mecanism Swift care adaugă un strat de logică la accesul și modificarea proprietăților fără duplicarea codului. În SwiftUI, Property Wrapper-urile au devenit baza gestionării stării vizualizărilor: @State, @Binding, @ObservedObject, @StateObject și @Environment. Potrivit documentației Swift (2025), împachetările de proprietăți reduc codul boilerplate în proiecte în medie cu 40%. Înțelegerea Property Wrapper este necesară fiecărui dezvoltator iOS pentru a lucra eficient cu framework-ul.

Principalele puncte

  • Property Wrapper — construcție sintactică Swift care adaugă logică la getter-ul și setter-ul proprietății
  • @State — împachetare pentru starea locală aparținând unei singure vizualizări
  • @Binding — conexiune bidirecțională cu sursa de date din vizualizarea părinte
  • @ObservedObject — observarea unui ObservableObject extern fără deținere
  • @StateObject — crearea și deținerea ObservableObject la nivelul vizualizării curente

Ce este Property Wrapper?

Property Wrapper este o construcție lingvistică Swift apărută în versiunea 5.1, care permite încapsularea logicii de acces la proprietate într-un tip separat. În loc să scrie gettere și settere repetitive în fiecare clasă, dezvoltatorul declară împachetarea o dată și o aplică prin adnotarea @ înaintea tipului. Swift împachetează automat proprietatea în tipul specificat, apelând metodele sale wrappedValue și projectedValue la citire și scriere. Conform Apple (WWDC 2019), Property Wrapper-urile au devenit abstracția cheie pentru SwiftUI.

Împachetarea de proprietate este o structură sau clasă cu atributul @propertyWrapper. În interior, un astfel de tip trebuie să implementeze proprietatea wrappedValue care returnează și setează valoarea curentă. Compilatorul Swift înlocuiește referințele la proprietatea originală cu apeluri la wrappedValue, ascunzând complet implementarea de codul apelant. Suplimentar, se poate defini projectedValue — o proiecție accesibilă prin simbolul $.

Avantajul Property Wrapper-urilor constă în reutilizarea logicii. De exemplu, se poate crea o împachetare pentru validarea emailului, memorarea valorilor sau sincronizarea cu stocarea — și aplica la orice proprietate din proiect. În SwiftUI, acest concept este utilizat omniprezent: fiecare mecanism de gestionare a stării este implementat ca un Property Wrapper separat.

Cum funcționează Property Wrapper în Swift?

La declararea unei proprietăți cu adnotarea @WrapperType var value: T, compilatorul Swift transformă codul. Creează o instanță a WrapperType și generează accesul la proprietate prin wrappedValue. Codul sursă let x = value se transformă în let x = _value.wrappedValue, iar value = newValue se transformă în _value.wrappedValue = newValue. Această transformare are loc în faza de compilare, fără overhead de runtime.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

    var wrappedValue: String {
        get { text }
        set { text = newValue.capitalized }
    }

    init(initialValue: String) {
        text = initialValue.capitalized
    }
}

În listare este prezentată împachetarea Capitalized, care formatează automat textul cu literă mare. La atribuirea valorii, setter-ul apelează capitalized înainte de salvare. Acum orice proprietate cu adnotarea @Capitalized va stoca doar text corect formatat. Această abordare elimină complet duplicarea codului de validare și formatare.

Proiecția (projectedValue) — un canal de comunicare suplimentar, accesibil prin prefixul $. În SwiftUI, această capacitate este utilizată omniprezent: $state oferă Binding, iar $observedObject returnează Published.Publisher. Proiecția permite împachetării să ofere o interfață extinsă fără a schimba accesul principal la valoare.

Principalele Property Wrapper-uri în SwiftUI

SwiftUI include cinci Property Wrapper-uri încorporate pentru gestionarea stării: @State, @Binding, @ObservedObject, @StateObject și @Environment. Fiecare dintre ele rezolvă o sarcină specifică și este utilizat în scenarii diferite. @State este destinat datelor locale simple, @Binding — pentru transmiterea referinței la date în vizualizări copil, @ObservedObject și @StateObject — pentru obiecte complexe, @Environment — pentru valori sistemice din ierarhie.

WrapperDestinațieDeținere
@StateStarea locală a unei vizualizăriVizualizarea curentă
@BindingConexiune bidirecțională cu părinteleVizualizarea părinte
@ObservedObjectObservarea unui obiect externProprietar extern
@StateObjectCrearea ObservableObjectVizualizarea curentă
@EnvironmentValori sistemice din ierarhieMediul SwiftUI

Alegerea unui Property Wrapper specific depinde de sursa datelor și de ciclul lor de viață. Dacă datele aparțin unei singure vizualizări și nu sunt necesare componentelor copil — utilizați @State. Dacă vizualizarea copil trebuie să modifice datele părintelui — aplicați @Binding. Pentru obiecte utilizate în mai multe vizualizări sunt potrivite @ObservedObject și @StateObject.

@State: starea locală a vizualizării

@State este un Property Wrapper pentru stocarea stării locale în interiorul unei singure vizualizări. SwiftUI gestionează automat memoria pentru proprietățile @State și redesenează vizualizarea la fiecare modificare. @State este potrivit pentru tipuri simple (String, Int, Bool, enum) și structuri care aparțin exclusiv vizualizării curente. La modificarea valorii, SwiftUI reexecută proprietatea body.

swift
struct CounterView: View {
    @State private var count: Int = 0

    var body: some View {
        VStack {
            Text("Număr: \(count)")
            Button("Incrementare") {
                count += 1
            }
        }
    }
}

În exemplu, proprietatea @State count stochează valoarea curentă a contorului. SwiftUI creează o zonă de stocare pentru această proprietate în heap și o leagă de ciclul de viață al CounterView. La apăsarea butonului, count crește cu 1, SwiftUI detectează modificarea și reexecută body, afișând noua valoare. Important: @State nu trebuie utilizat pentru tipuri referențiale complexe — pentru acestea sunt destinate @StateObject și @ObservedObject.

@Binding: conexiune bidirecțională între vizualizări

@Binding creează o referință la sursa de date aparținând altei vizualizări. Binding nu stochează valoarea independent — citește și scrie date prin @State, @StateObject sau un alt Binding transmis de la părinte. Acest lucru permite componentelor copil să modifice starea strămoșului fără deținerea directă a datelor și fără callback-uri.

swift
struct ToggleSwitch: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

În listare, ToggleSwitch primește un @BindingBool de la vizualizarea părinte. Părintele creează @State var isToggleOn = false și transmite $isToggleOn în inițializatorul ToggleSwitch. Când utilizatorul comută Toggle în interiorul vizualizării copil, modificarea se reflectă imediat în @State-ul părintelui. Mecanismul Binding elimină complet necesitatea delegatelor sau closure-urilor pentru transmiterea modificărilor în susul ierarhiei.

@ObservedObject și @StateObject: date externe

@ObservedObject este un Property Wrapper pentru observarea unei instanțe ObservableObject transmisă din exterior. Vizualizarea nu deține acest obiect — este creat în componenta părinte sau injectat prin Environment. Când orice proprietate @Published din interiorul ObservableObject se modifică, SwiftUI redesenează toate vizualizările abonate prin @ObservedObject.

@StateObject este o împachetare pentru crearea și deținerea ObservableObject direct în vizualizare. Spre deosebire de @ObservedObject, @StateObject garantează o singură instanță a obiectului pe întreg ciclul de viață al vizualizării. Chiar dacă SwiftUI re-creează structura vizualizării (ceea ce se întâmplă frecvent), @StateObject păstrează obiectul existent și nu reapelează inițializatorul.

swift
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("Salut, \(settings.username)")
    }
}

În exemplu, ProfileView creează UserSettings prin @StateObject, devenind proprietarul obiectului. ChildProfileView primește aceeași instanță prin @ObservedObject — observă, dar nu gestionează ciclul de viață. La modificarea username, ambele vizualizări se actualizează. Dacă ChildProfileView ar fi folosit @StateObject în loc de @ObservedObject, la fiecare randare s-ar crea o nouă instanță cu valoarea inițială.

Regula cheie: @StateObject se aplică în vizualizarea care creează obiectul (sursa adevărului), iar @ObservedObject — în vizualizarea care primește obiectul gata făcut de la părinte. Încălcarea acestei reguli duce la pierderea stării sau la re-crearea neașteptată a datelor.

Crearea propriului Property Wrapper

Swift permite crearea de Property Wrapper-uri personalizate pentru orice logică repetitivă de acces la proprietăți. Este suficient să declarați o structură sau clasă cu atributul @propertyWrapper și să implementați wrappedValue. Mai jos este prezentată împachetarea UserDefaultsWrapper, care sincronizează automat valoarea cu UserDefaults.

swift
@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
}

Împachetarea UserDefaultsWrapper utilizează un generic T pentru a lucra cu orice tip de date suportat de UserDefaults. Getter-ul citește valoarea după cheie, setter-ul scrie. Aplicarea @UserDefaultsWrapper(key:defaultValue:) la proprietatea theme o leagă automat de stocare — întreaga logică de lucru cu UserDefaults este ascunsă în interiorul împachetării. Acesta este un exemplu tipic de reducere a codului boilerplate cu ajutorul Property Wrapper-urilor.

La crearea propriilor împachetări, este important să se ia în considerare performanța. Deoarece getter-ul și setter-ul sunt apelate la fiecare acces la proprietate, nu trebuie plasate operații I/O grele în wrappedValue. Pentru stocarea asincronă a datelor, este mai bine să combinați Property Wrapper-urile cu ObservableObject și @Published.

Întrebări frecvente

Care este diferența dintre @State și @StateObject?

@State este destinat tipurilor simple (String, Int, Bool) și structurilor, iar @StateObject — tipurilor referențiale care implementează ObservableObject. @State stochează valoarea direct în SwiftUI, @StateObject gestionează o instanță a clasei în heap.

Se poate folosi @Binding fără @State?

Da, @Binding poate fi creat din @StateObject, @ObservedObject sau dintr-un alt Binding cu ajutorul proiecției $. De asemenea, Binding se inițializează din ObservableObject prin $object.$publishedProperty sau din InlineBinding prin Binding.constant(value).

Ce Property Wrapper să aleg pentru date utilizate în întreaga aplicație?

Pentru date globale, utilizați @EnvironmentObject sau injectați ObservableObject prin EnvironmentValues. @StateObject este potrivit pentru vizualizarea rădăcină cu transmiterea ulterioară prin @ObservedObject către componentele copil.

De ce @ObservedObject poate re-crea datele?

@ObservedObject nu deține obiectul — dacă vizualizarea părinte este re-creată și transmite o nouă instanță, @ObservedObject comută pe ea. Pentru a evita pierderea stării, vizualizarea deținătoare trebuie să utilizeze @StateObject.

Se poate crea un Property Wrapper pentru operații asincrone?

Da, dar este mai simplu să utilizați o combinație de ObservableObject cu @Published și funcții async în interiorul clasei. Property Wrapper este sincron prin natură — wrappedValue este calculat la fiecare acces, ceea ce nu este potrivit pentru operații de lungă durată.

Concluzii

  • Property Wrapper — construcție sintactică Swift 5.1 pentru încapsularea logicii de acces la proprietăți fără duplicarea codului
  • @State — stare locală pentru tipuri simple, redesenează automat vizualizarea la modificare
  • @Binding — referință la datele vizualizării părinte fără deținere, accesibilă prin proiecția $
  • @StateObject — deținerea ObservableObject cu garanția unei singure instanțe pe ciclul de viață
  • @ObservedObject — observarea ObservableObject transmis din componenta părinte
  • @Environment — acces la obiectele sistemului de mediu SwiftUI
  • Împachetări personalizate se creează prin atributul @propertyWrapper și implementarea wrappedValue

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și