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 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.
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.
@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
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.
| Wrapper | Propósito | Posse |
|---|---|---|
| @State | Estado local de uma única view | View atual |
| @Binding | Conexão bidirecional com o pai | View pai |
| @ObservedObject | Observação de um objeto externo | Proprietário externo |
| @StateObject | Criação de ObservableObject | View atual |
| @Environment | Valores do sistema da hierarquia | Ambiente 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 é 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.
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 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.
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 é 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.
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.
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.
@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
@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.
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).
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.
@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.
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
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.
Leia também