Property Wrapper: conceitos-chave, sintaxe e exemplos no SwiftUI

Autor: IT Sectr Publicado: 2026-06-19 Tempo de leitura: 8 min

Property Wrapper é um mecanismo do Swift que adiciona uma camada de lógica ao acesso e modificação de propriedades sem duplicar código. No SwiftUI, os Property Wrappers se tornaram a base do gerenciamento de estado das views: @State, @Binding, @ObservedObject, @StateObject e @Environment. De acordo com a documentação do Swift (2025), os wrappers de propriedades reduzem o código repetitivo em projetos em média 40%. Compreender Property Wrapper é essencial para todo desenvolvedor iOS trabalhar eficazmente com o framework.

Pontos principais

  • Property Wrapper — uma construção sintática do Swift que adiciona lógica ao getter e setter de uma propriedade
  • @State — wrapper para estado local pertencente a uma única view
  • @Binding — conexão bidirecional com uma fonte de dados da view pai
  • @ObservedObject — observação de um ObservableObject externo sem posse
  • @StateObject — criação e posse de ObservableObject no nível da view atual

O que é Property Wrapper?

Property Wrapper — uma construção da linguagem Swift, introduzida na versão 5.1, que permite encapsular a lógica de acesso a propriedades em um tipo separado. Em vez de escrever getters e setters repetitivos em cada classe, o desenvolvedor declara o wrapper uma vez e o aplica através da anotação @ antes do tipo. O Swift automaticamente envolve a propriedade no tipo especificado, chamando seus métodos wrappedValue e projectedValue ao ler e escrever. De acordo com a Apple (WWDC 2019), os Property Wrappers se tornaram uma abstração chave para o SwiftUI.

Um wrapper de propriedade é uma estrutura ou classe com o atributo @propertyWrapper. Internamente, tal tipo deve implementar a propriedade wrappedValue, que retorna e define o valor real. O compilador Swift substitui os acessos à propriedade original por chamadas a wrappedValue, ocultando completamente a implementação do código chamador. Adicionalmente, pode-se definir projectedValue — uma projeção acessível através do símbolo $.

A vantagem dos Property Wrappers reside na reutilização da lógica. Por exemplo, pode-se criar um wrapper para validação de email, armazenamento em cache de valores ou sincronização com armazenamento — e aplicá-lo a qualquer propriedade do projeto. No SwiftUI, este conceito é usado em toda parte: cada mecanismo de gerenciamento de estado é implementado como um Property Wrapper separado.

Como o Property Wrapper funciona no Swift?

Ao declarar uma propriedade com a anotação @WrapperType var value: T, o compilador Swift transforma o código. Ele cria uma instância de WrapperType e gera o acesso à propriedade através de wrappedValue. O código fonte let x = value torna-se let x = _value.wrappedValue, e value = newValue torna-se _value.wrappedValue = newValue. Esta transformação ocorre em tempo de compilação, sem sobrecarga em tempo de execução.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

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

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

A listagem mostra o wrapper Capitalized, que converte automaticamente uma string para o formato com letra maiúscula. Ao atribuir um valor, o setter chama capitalized antes de salvar. Agora qualquer propriedade com a anotação @Capitalized armazenará apenas texto formatado corretamente. Esta abordagem elimina completamente a duplicação de código de validação e formatação.

A projeção (projectedValue) — um canal de comunicação adicional acessível através do prefixo $. No SwiftUI, este recurso é usado em toda parte: $state fornece Binding, e $observedObject retorna Published.Publisher. A projeção permite que o wrapper forneça uma interface estendida sem alterar o acesso básico ao valor.

Principais Property Wrappers no SwiftUI

O SwiftUI inclui cinco Property Wrappers incorporados para gerenciamento de estado: @State, @Binding, @ObservedObject, @StateObject e @Environment. Cada um resolve uma tarefa específica e é usado em diferentes cenários. @State é projetado para dados locais simples, @Binding — para passar uma referência a dados para views filhas, @ObservedObject e @StateObject — para objetos complexos, @Environment — para valores do sistema da hierarquia.

WrapperPropósitoPosse
@StateEstado local de uma única viewView atual
@BindingConexão bidirecional com o paiView pai
@ObservedObjectObservação de um objeto externoProprietário externo
@StateObjectCriação de ObservableObjectView atual
@EnvironmentValores do sistema da hierarquiaAmbiente SwiftUI

A escolha de um Property Wrapper específico depende da fonte de dados e seu ciclo de vida. Se os dados pertencem a uma única view e não são necessários para componentes filhos — use @State. Se uma view filha precisa modificar os dados do pai — use @Binding. Para objetos usados em múltiplas views, @ObservedObject e @StateObject são adequados.

@State: estado local da view

@State é um Property Wrapper para armazenar estado local dentro de uma única view. O SwiftUI gerencia automaticamente a memória para propriedades @State e redesenha a view a cada alteração. @State é adequado para tipos simples (String, Int, Bool, enum) e estruturas que pertencem exclusivamente à view atual. Quando o valor muda, o SwiftUI reexecuta a propriedade body.

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

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

No exemplo, a propriedade @State count armazena o valor atual do contador. O SwiftUI cria uma área de armazenamento para esta propriedade no heap e a vincula ao ciclo de vida da CounterView. Ao pressionar o botão, count aumenta em 1, o SwiftUI detecta a mudança e reexecuta body, exibindo o novo valor. Importante: @State não deve ser usado para tipos de referência complexos — para isso existem @StateObject e @ObservedObject.

@Binding: conexão bidirecional entre views

@Binding cria uma referência a uma fonte de dados pertencente a outra view. Binding não armazena um valor por si só — ele lê e escreve dados através de @State, @StateObject ou outro Binding passado do pai. Isso permite que componentes filhos modifiquem o estado do ancestral sem possuir os dados diretamente e sem callbacks.

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

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

Na listagem, ToggleSwitch recebe @BindingBool da view pai. O pai cria @State var isToggleOn = false e passa $isToggleOn para o inicializador do ToggleSwitch. Quando o usuário alterna o interruptor dentro da view filha, a mudança é imediatamente refletida no @State do pai. O mecanismo Binding elimina completamente a necessidade de delegados ou closures para passar mudanças para cima na hierarquia.

@ObservedObject e @StateObject: dados externos

@ObservedObject é um Property Wrapper para observar uma instância de ObservableObject passada de fora. A view não possui este objeto — ele é criado no componente pai ou injetado através do Environment. Quando qualquer propriedade @Published dentro do ObservableObject muda, o SwiftUI redesenha todas as views inscritas via @ObservedObject.

@StateObject — um wrapper para criar e possuir um ObservableObject diretamente na view. Diferente do @ObservedObject, o @StateObject garante uma única instância do objeto durante todo o ciclo de vida da view. Mesmo que o SwiftUI recrie a estrutura da view (o que acontece frequentemente), o @StateObject preserva o objeto existente e não chama o inicializador novamente.

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

No exemplo, ProfileView cria UserSettings via @StateObject, tornando-se o proprietário do objeto. ChildProfileView recebe a mesma instância via @ObservedObject — observa mas não gerencia o ciclo de vida. Quando username muda, ambas as views são atualizadas. Se ChildProfileView usasse @StateObject em vez de @ObservedObject, uma nova instância com o valor inicial seria criada a cada renderização.

A regra chave: @StateObject é usado na view que cria o objeto (fonte da verdade), enquanto @ObservedObject é usado na view que recebe um objeto já criado do pai. Violar esta regra leva à perda de estado ou recriações inesperadas de dados.

Criação de um Property Wrapper personalizado

O Swift permite criar Property Wrappers personalizados para qualquer lógica repetitiva de acesso a propriedades. Basta declarar uma estrutura ou classe com o atributo @propertyWrapper e implementar wrappedValue. Abaixo é mostrado o wrapper UserDefaultsWrapper, que sincroniza automaticamente o valor com o 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
}

O wrapper UserDefaultsWrapper usa um genérico T para funcionar com qualquer tipo de dado suportado pelo UserDefaults. O getter lê o valor pela chave, o setter escreve. Aplicar @UserDefaultsWrapper(key:defaultValue:) à propriedade theme a vincula automaticamente ao armazenamento — toda a lógica do UserDefaults fica oculta dentro do wrapper. Este é um exemplo típico de redução de código repetitivo com Property Wrappers.

Ao criar wrappers personalizados, é importante considerar o desempenho. Como o getter e o setter são chamados a cada acesso à propriedade, operações pesadas de E/S não devem ser colocadas em wrappedValue. Para armazenamento de dados assíncrono, é melhor combinar Property Wrappers com ObservableObject e @Published.

Perguntas frequentes

Qual a diferença entre @State e @StateObject?

@State é projetado para tipos simples (String, Int, Bool) e estruturas, enquanto @StateObject é para tipos de referência que implementam ObservableObject. @State armazena o valor diretamente no SwiftUI, @StateObject gerencia uma instância de classe no heap.

Pode-se usar @Binding sem @State?

Sim, @Binding pode ser criado a partir de @StateObject, @ObservedObject ou de outro Binding usando a projeção $. Binding também pode ser inicializado de ObservableObject através de $object.$publishedProperty ou de InlineBinding via Binding.constant(value).

Qual Property Wrapper escolher para dados usados em toda a aplicação?

Para dados globais, use @EnvironmentObject ou injete ObservableObject através de EnvironmentValues. @StateObject é adequado para a view raiz com posterior transmissão via @ObservedObject para componentes filhos.

Por que @ObservedObject pode recriar dados?

@ObservedObject não possui o objeto — se a view pai for recriada e passar uma nova instância, @ObservedObject mudará para ela. Para evitar perda de estado, a view proprietária deve usar @StateObject.

Pode-se criar um Property Wrapper para operações assíncronas?

Sim, mas é mais fácil usar uma combinação de ObservableObject com @Published e funções assíncronas dentro da classe. Property Wrapper é síncrono por natureza — wrappedValue é calculado a cada acesso, o que não é adequado para operações de longa duração.

Resumo

  • Property Wrapper — construção sintática do Swift 5.1 para encapsular lógica de acesso a propriedades sem duplicação de código
  • @State — estado local para tipos simples, redesenha automaticamente a view ao mudar
  • @Binding — referência aos dados da view pai sem posse, acessível através da projeção $
  • @StateObject — posse de ObservableObject com garantia de instância única por ciclo de vida
  • @ObservedObject — observação de ObservableObject passado do componente pai
  • @Environment — acesso aos objetos do ambiente do sistema SwiftUI
  • Wrappers personalizados são criados através do atributo @propertyWrapper e implementação de wrappedValue

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também