Property Wrapper: concetti chiave, sintassi ed esempi in SwiftUI

Autore: IT Sectr Pubblicato: 2026-06-19 Tempo di lettura: 8 min

Property Wrapper è un meccanismo Swift che aggiunge un livello di logica all'accesso e alla modifica delle proprietà senza duplicare codice. In SwiftUI, i Property Wrappers sono diventati la base della gestione dello stato delle viste: @State, @Binding, @ObservedObject, @StateObject e @Environment. Secondo la documentazione Swift (2025), i wrapper di proprietà riducono il codice boilerplate nei progetti in media del 40%. Comprendere Property Wrapper è essenziale per ogni sviluppatore iOS per lavorare efficacemente con il framework.

Punti chiave

  • Property Wrapper — una costruzione sintattica Swift che aggiunge logica al getter e setter di una proprietà
  • @State — wrapper per lo stato locale appartenente a una singola vista
  • @Binding — connessione bidirezionale con una fonte di dati dalla vista padre
  • @ObservedObject — osservazione di un ObservableObject esterno senza possesso
  • @StateObject — creazione e possesso di ObservableObject a livello della vista corrente

Cos'è Property Wrapper?

Property Wrapper — una costruzione del linguaggio Swift, introdotta nella versione 5.1, che consente di incapsulare la logica di accesso alle proprietà in un tipo separato. Invece di scrivere getter e setter ripetitivi in ogni classe, lo sviluppatore dichiara il wrapper una volta e lo applica tramite l'annotazione @ davanti al tipo. Swift automaticamente avvolge la proprietà nel tipo specificato, chiamando i suoi metodi wrappedValue e projectedValue durante la lettura e scrittura. Secondo Apple (WWDC 2019), i Property Wrappers sono diventati un'astrazione chiave per SwiftUI.

Un wrapper di proprietà è una struttura o classe con l'attributo @propertyWrapper. All'interno, tale tipo deve implementare la proprietà wrappedValue, che restituisce e imposta il valore effettivo. Il compilatore Swift sostituisce gli accessi alla proprietà originale con chiamate a wrappedValue, nascondendo completamente l'implementazione dal codice chiamante. Inoltre, si può definire projectedValue — una proiezione accessibile tramite il simbolo $.

Il vantaggio dei Property Wrappers risiede nella riutilizzabilità della logica. Ad esempio, si può creare un wrapper per la validazione email, la memorizzazione nella cache di valori o la sincronizzazione con l'archivio — e applicarlo a qualsiasi proprietà del progetto. In SwiftUI, questo concetto è usato ovunque: ogni meccanismo di gestione dello stato è implementato come un Property Wrapper separato.

Come funziona Property Wrapper in Swift?

Dichiarando una proprietà con l'annotazione @WrapperType var value: T, il compilatore Swift trasforma il codice. Crea un'istanza di WrapperType e genera l'accesso alla proprietà tramite wrappedValue. Il codice sorgente let x = value diventa let x = _value.wrappedValue, e value = newValue diventa _value.wrappedValue = newValue. Questa trasformazione avviene in fase di compilazione, senza overhead a runtime.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

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

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

Il listing mostra il wrapper Capitalized, che converte automaticamente una stringa in formato con lettera maiuscola. Nell'assegnare un valore, il setter chiama capitalized prima di salvare. Ora qualsiasi proprietà con l'annotazione @Capitalized memorizzerà solo testo correttamente formattato. Questo approccio elimina completamente la duplicazione del codice di validazione e formattazione.

La proiezione (projectedValue) — un canale di comunicazione aggiuntivo accessibile tramite il prefisso $. In SwiftUI, questa funzionalità è usata ovunque: $state fornisce Binding, e $observedObject restituisce Published.Publisher. La proiezione permette al wrapper di fornire un'interfaccia estesa senza modificare l'accesso di base al valore.

Principali Property Wrappers in SwiftUI

SwiftUI include cinque Property Wrappers incorporati per la gestione dello stato: @State, @Binding, @ObservedObject, @StateObject e @Environment. Ognuno risolve un compito specifico e viene usato in diversi scenari. @State è progettato per dati locali semplici, @Binding — per passare un riferimento ai dati alle viste figlie, @ObservedObject e @StateObject — per oggetti complessi, @Environment — per valori di sistema dalla gerarchia.

WrapperScopoPossesso
@StateStato locale di una singola vistaVista corrente
@BindingConnessione bidirezionale con il genitoreVista padre
@ObservedObjectOsservazione di un oggetto esternoProprietario esterno
@StateObjectCreazione di ObservableObjectVista corrente
@EnvironmentValori di sistema dalla gerarchiaAmbiente SwiftUI

La scelta di uno specifico Property Wrapper dipende dalla fonte dei dati e dal loro ciclo di vita. Se i dati appartengono a una singola vista e non sono necessari ai componenti figli — usa @State. Se una vista figlia deve modificare i dati del genitore — usa @Binding. Per oggetti utilizzati in più viste, sono adatti @ObservedObject e @StateObject.

@State: stato locale della vista

@State è un Property Wrapper per memorizzare lo stato locale all'interno di una singola vista. SwiftUI gestisce automaticamente la memoria per le proprietà @State e ridisegna la vista a ogni modifica. @State è adatto a tipi semplici (String, Int, Bool, enum) e strutture che appartengono esclusivamente alla vista corrente. Quando il valore cambia, SwiftUI riesegue la proprietà body.

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

    var body: some View {
        VStack {
            Text("Conteggio: \(count)")
            Button("Incrementa") {
                count += 1
            }
        }
    }
}

Nell'esempio, la proprietà @State count memorizza il valore corrente del contatore. SwiftUI crea un'area di memorizzazione per questa proprietà nello heap e la lega al ciclo di vita di CounterView. Quando il pulsante viene premuto, count aumenta di 1, SwiftUI rileva il cambiamento e riesegue body, mostrando il nuovo valore. Importante: @State non dovrebbe essere usato per tipi di riferimento complessi — a questo servono @StateObject e @ObservedObject.

@Binding: connessione bidirezionale tra viste

@Binding crea un riferimento a una fonte di dati appartenente a un'altra vista. Binding non memorizza un valore da solo — legge e scrive dati tramite @State, @StateObject o un altro Binding passato dal genitore. Questo permette ai componenti figli di modificare lo stato dell'antenato senza possedere direttamente i dati e senza callback.

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

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

Nel listing, ToggleSwitch riceve @BindingBool dalla vista padre. Il genitore crea @State var isToggleOn = false e passa $isToggleOn all'inizializzatore di ToggleSwitch. Quando l'utente aziona l'interruttore all'interno della vista figlia, la modifica si riflette immediatamente nel @State del genitore. Il meccanismo Binding elimina completamente la necessità di delegati o closures per propagare le modifiche verso l'alto nella gerarchia.

@ObservedObject e @StateObject: dati esterni

@ObservedObject è un Property Wrapper per osservare un'istanza di ObservableObject passata dall'esterno. La vista non possiede questo oggetto — viene creato nel componente padre o iniettato tramite Environment. Quando qualsiasi proprietà @Published all'interno di ObservableObject cambia, SwiftUI ridisegna tutte le viste sottoscritte tramite @ObservedObject.

@StateObject — un wrapper per creare e possedere un ObservableObject direttamente nella vista. A differenza di @ObservedObject, @StateObject garantisce una singola istanza dell'oggetto per l'intero ciclo di vita della vista. Anche se SwiftUI ricrea la struttura della vista (cosa che accade frequentemente), @StateObject preserva l'oggetto esistente e non chiama l'inizializzatore di nuovo.

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

Nell'esempio, ProfileView crea UserSettings tramite @StateObject, diventando proprietario dell'oggetto. ChildProfileView riceve la stessa istanza tramite @ObservedObject — osserva ma non gestisce il ciclo di vita. Quando username cambia, entrambe le viste vengono aggiornate. Se ChildProfileView usasse @StateObject invece di @ObservedObject, a ogni rendering verrebbe creata una nuova istanza con il valore iniziale.

La regola chiave: @StateObject viene usato nella vista che crea l'oggetto (fonte di verità), mentre @ObservedObject viene usato nella vista che riceve un oggetto già creato dal genitore. Violare questa regola porta alla perdita dello stato o a ricreazioni impreviste dei dati.

Creazione di un Property Wrapper personalizzato

Swift permette di creare Property Wrappers personalizzati per qualsiasi logica ripetitiva di accesso alle proprietà. Basta dichiarare una struttura o classe con l'attributo @propertyWrapper e implementare wrappedValue. Qui sotto è mostrato il wrapper UserDefaultsWrapper, che sincronizza automaticamente il valore con 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
}

Il wrapper UserDefaultsWrapper usa un generico T per funzionare con qualsiasi tipo di dato supportato da UserDefaults. Il getter legge il valore per chiave, il setter lo scrive. Applicare @UserDefaultsWrapper(key:defaultValue:) alla proprietà theme la lega automaticamente all'archivio — tutta la logica di UserDefaults è nascosta all'interno del wrapper. Questo è un tipico esempio di riduzione del codice boilerplate con i Property Wrappers.

Quando si creano wrapper personalizzati, è importante considerare le prestazioni. Poiché il getter e il setter vengono chiamati a ogni accesso alla proprietà, non si dovrebbero inserire operazioni I/O pesanti in wrappedValue. Per l'archiviazione asincrona dei dati, è meglio combinare i Property Wrappers con ObservableObject e @Published.

Domande frequenti

Qual è la differenza tra @State e @StateObject?

@State è progettato per tipi semplici (String, Int, Bool) e strutture, mentre @StateObject è per tipi di riferimento che implementano ObservableObject. @State memorizza il valore direttamente in SwiftUI, @StateObject gestisce un'istanza di classe nello heap.

Si può usare @Binding senza @State?

Sì, @Binding può essere creato da @StateObject, @ObservedObject o da un altro Binding tramite la proiezione $. Binding può anche essere inizializzato da ObservableObject tramite $object.$publishedProperty o da InlineBinding tramite Binding.constant(value).

Quale Property Wrapper scegliere per dati usati in tutta l'applicazione?

Per dati globali, usa @EnvironmentObject o inietta ObservableObject tramite EnvironmentValues. @StateObject è adatto per la vista radice con successiva trasmissione tramite @ObservedObject ai componenti figli.

Perché @ObservedObject può ricreare i dati?

@ObservedObject non possiede l'oggetto — se la vista padre viene ricreata e passa una nuova istanza, @ObservedObject passerà a quella. Per evitare la perdita di stato, la vista proprietaria dovrebbe usare @StateObject.

Si può creare un Property Wrapper per operazioni asincrone?

Sì, ma è più facile usare una combinazione di ObservableObject con @Published e funzioni asincrone all'interno della classe. Property Wrapper è sincrono per natura — wrappedValue viene calcolato a ogni accesso, il che non è adatto per operazioni di lunga durata.

Riepilogo

  • Property Wrapper — costruzione sintattica di Swift 5.1 per incapsulare la logica di accesso alle proprietà senza duplicazione di codice
  • @State — stato locale per tipi semplici, ridisegna automaticamente la vista al cambiamento
  • @Binding — riferimento ai dati della vista padre senza possesso, accessibile tramite la proiezione $
  • @StateObject — possesso di ObservableObject con garanzia di istanza unica per ciclo di vita
  • @ObservedObject — osservazione di ObservableObject passato dal componente padre
  • @Environment — accesso agli oggetti dell'ambiente di sistema SwiftUI
  • Wrapper personalizzati vengono creati tramite l'attributo @propertyWrapper e l'implementazione di wrappedValue

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche