@Observable no SwiftUI: Guia Completo para Migrar de ObservableObject (iOS 26)

Aprenda a migrar de ObservableObject para @Observable no SwiftUI (iOS 26): rastreamento por propriedade, @Bindable, @State, @Environment e as armadilhas mais comuns, com exemplos de codigo praticos.

@Observable no SwiftUI: Guia 2026

Atualizado: 16 de junho de 2026

A macro @Observable e a maneira moderna de tornar uma classe Swift observavel pelo SwiftUI: voce anota a classe com @Observable e pronto, as propriedades passam a notificar automaticamente as views que as leem, sem precisar de ObservableObject, @Published ou @ObservedObject. Introduzida com o framework Observation no iOS 17 e refinada no iOS 26, ela traz rastreamento por propriedade, melhor desempenho e integracao nativa com @State, @Bindable e @Environment.

Honestamente, depois de migrar tres apps de producao para @Observable nos ultimos meses, posso dizer que a diferenca de fluidez em listas grandes e visivel a olho nu. Vou mostrar o que aprendi (incluindo as armadilhas que custaram horas).

  • A macro @Observable substitui o protocolo ObservableObject e o property wrapper @Published em projetos iOS 17+ e iOS 26.
  • O rastreamento e por propriedade: apenas as views que leem a propriedade alterada sao reinvalidadas, reduzindo redraws desnecessarios.
  • Use @State para criar e armazenar um modelo @Observable dentro de uma view; use @Bindable quando precisar de Binding para subviews.
  • Anote propriedades nao rastreaveis com @ObservationIgnored para evitar invalidacoes em caches, tokens e timers internos.
  • A migracao de ObservableObject para @Observable normalmente envolve trocar 4 anotacoes por uma e ajustar property wrappers nas views consumidoras.
  • Combine @Observable com @MainActor e o modelo de concorrencia acessivel do Swift 6.2 para garantir mutacoes seguras na UI.

O que e @Observable e como funciona

A macro @Observable faz parte do framework Observation, introduzido na WWDC23 e disponivel a partir do iOS 17, macOS 14, watchOS 10 e tvOS 17. Quando voce anota uma classe com @Observable, o compilador gera, em tempo de compilacao, o codigo necessario para que cada propriedade armazenada seja rastreada individualmente pelo SwiftUI. Internamente, a macro adiciona conformidade ao protocolo Observable, expande cada propriedade com observadores willSet/didSet e instala um _$observationRegistrar que registra acessos e mutacoes.

Na pratica, isso significa que o SwiftUI so reinvalida uma view se essa view tiver lido a propriedade exata que mudou. Antes, com ObservableObject, qualquer alteracao em uma propriedade @Published disparava objectWillChange para o objeto inteiro, e todas as views observadoras eram reinvalidadas, mesmo as que so liam outras propriedades. Esse rastreamento granular e o motivo principal pelo qual times migram para @Observable em apps com listas longas ou telas com muitos campos.

import Observation
import SwiftUI

@Observable
final class CarrinhoModel {
    var itens: [Item] = []
    var cupom: String = ""
    var total: Decimal { itens.reduce(0) { $0 + $1.preco } }
}

struct CarrinhoView: View {
    @State private var carrinho = CarrinhoModel()

    var body: some View {
        List(carrinho.itens) { item in
            Text(item.nome)
        }
    }
}

Repare que nao ha @Published, nao ha ObservableObject, e a view usa @State para guardar o modelo (antes seria @StateObject). Essa simplificacao e deliberada: a Apple unificou o ciclo de vida de modelos de valor e de referencia sob o mesmo property wrapper.

@Observable vs ObservableObject: comparacao direta

A tabela abaixo resume as diferencas que mais pesam no dia a dia ao decidir migrar. Em geral, @Observable e a escolha padrao para qualquer novo codigo iOS 17+. O velho ObservableObject so continua util quando voce precisa interoperar com bibliotecas Combine antigas ou suportar deployment targets anteriores.

Caracteristica@Observable (iOS 17+)ObservableObject (iOS 13+)
Anotacao por propriedadeNenhuma, automatica@Published em cada propriedade
Granularidade do trackingPor propriedade lidaPor objeto (qualquer mudanca invalida)
Armazenamento na view@State@StateObject
Observar de foraAcesso direto a propriedade@ObservedObject
Bindings@Bindable@ObservedObject com $
Injecao via ambiente.environment(model) + @Environment.environmentObject + @EnvironmentObject
Suporte a CombineNao nativoobjectWillChange publisher
Minimo deployment targetiOS 17 / macOS 14iOS 13 / macOS 10.15

Como migrar de ObservableObject para @Observable

A migracao em si e mecanica e geralmente leva poucos minutos por modelo. Os passos a seguir cobrem os casos que aparecem em praticamente qualquer base de codigo SwiftUI moderno e tambem ajudam a manter o app funcionando durante a transicao gradual entre os dois modelos de observacao.

1. Importar Observation e anotar a classe

Adicione import Observation no topo do arquivo e troque a conformidade : ObservableObject pela macro @Observable antes da declaracao da classe:

// Antes
import Combine

final class PerfilModel: ObservableObject {
    @Published var nome: String = ""
    @Published var email: String = ""
}

// Depois
import Observation

@Observable
final class PerfilModel {
    var nome: String = ""
    var email: String = ""
}

Remova import Combine se ele nao estiver sendo usado para outra coisa. As anotacoes @Published tambem somem, todas as propriedades armazenadas viram rastreaveis por padrao.

2. Atualizar os property wrappers nas views

O segundo passo e trocar os wrappers que armazenam ou observam o modelo nas views consumidoras. O mapeamento e direto:

  • @StateObject vira @State
  • @ObservedObject vira propriedade simples (sem wrapper) quando passada por parametro
  • @ObservedObject com bindings ($model.nome) vira @Bindable
  • @EnvironmentObject vira @Environment(Tipo.self) + .environment(model)
// Antes
struct PerfilView: View {
    @StateObject private var modelo = PerfilModel()
    var body: some View { EditorView(modelo: modelo) }
}

struct EditorView: View {
    @ObservedObject var modelo: PerfilModel
    var body: some View { TextField("Nome", text: $modelo.nome) }
}

// Depois
struct PerfilView: View {
    @State private var modelo = PerfilModel()
    var body: some View { EditorView(modelo: modelo) }
}

struct EditorView: View {
    @Bindable var modelo: PerfilModel
    var body: some View { TextField("Nome", text: $modelo.nome) }
}

3. Substituir EnvironmentObject por Environment

A API de ambiente tambem muda. Em vez de .environmentObject(session), use .environment(session), e do lado consumidor passe o tipo para @Environment:

@main
struct MeuApp: App {
    @State private var session = SessionModel()
    var body: some Scene {
        WindowGroup {
            ContentView().environment(session)
        }
    }
}

struct ContentView: View {
    @Environment(SessionModel.self) private var session
    var body: some View { Text(session.usuario) }
}

Usando @Bindable, @State e @Environment

O property wrapper @Bindable existe especificamente para criar Binding a partir de uma propriedade de um tipo @Observable. Sem ele, voce consegue ler e mutar propriedades diretamente, mas nao consegue passar $modelo.campo para componentes como TextField, Toggle ou NavigationLink. Ja o @State assumiu o papel duplo: para tipos de valor (structs) ele cria um buffer interno; para classes @Observable ele mantem a referencia viva ao longo do ciclo de vida da view, como o antigo @StateObject fazia.

Uma confusao frequente e quando aplicar cada um. A regra pratica que uso e simples: @State para possuir o modelo (quem cria), passe-o como parametro normal para subviews que apenas leem, e @Bindable dentro de uma subview quando ela precisa de bindings $ para os controles SwiftUI. Se o modelo for compartilhado por muitas telas, prefira @Environment(Tipo.self).

struct FormularioView: View {
    let modelo: PerfilModel       // so le, sem wrapper
    @Bindable var preferencias: PreferenciasModel  // precisa de $bindings

    var body: some View {
        Form {
            Text(modelo.nome)
            Toggle("Notificacoes", isOn: $preferencias.notificacoesAtivas)
        }
    }
}

Para padroes mais avancados de injecao de dependencias e composicao de modelos, vale revisar como o iOS 26 trata estado em conjunto com o novo design do sistema. Nosso guia de Liquid Glass no SwiftUI mostra como animacoes e materiais reagem ao estado @Observable em tempo real.

Quando usar @ObservationIgnored

Nem toda propriedade dentro de uma classe @Observable precisa participar do rastreamento. Caches internos, tokens de cancelamento, timers, subscribers de Combine e referencias fracas sao bons candidatos a @ObservationIgnored. Quando voce marca uma propriedade com esse atributo, a macro pula a geracao do registrar para ela: alteracoes nao disparam invalidacao de view, e leituras nao criam dependencias de tracking.

@Observable
final class FeedModel {
    var posts: [Post] = []           // rastreado
    var carregando: Bool = false     // rastreado

    @ObservationIgnored
    private var cache: [UUID: Post] = [:]  // nao rastreado

    @ObservationIgnored
    private var tarefa: Task<Void, Never>?  // nao rastreado
}

Vale lembrar uma coisa: @ObservationIgnored nao impede o acesso a propriedade. Ele apenas remove o rastreamento. Voce continua podendo ler e escrever normalmente, inclusive de dentro de metodos da classe. A diferenca e que nenhuma view sera notificada quando o valor mudar, o que e exatamente o que voce quer para estado interno.

@Observable com concorrencia e @MainActor

Como o SwiftUI atualiza views na main actor, faz sentido isolar a maioria dos modelos @Observable em @MainActor. Isso garante que mutacoes as propriedades rastreadas acontecam na thread principal, evitando data races e os avisos de concorrencia estrita do Swift 6.2. A macro @Observable e totalmente compativel com isolamento de actor, basta anotar a classe:

@MainActor
@Observable
final class TimelineModel {
    var tweets: [Tweet] = []
    var carregando = false

    func recarregar() async {
        carregando = true
        defer { carregando = false }
        tweets = await TweetAPI.fetch()
    }
}

Quando o modelo precisa fazer trabalho pesado fora da main actor (parsing, criptografia, banco de dados), use nonisolated em funcoes especificas e marque os pontos de mutacao com await de volta no actor principal. Para um tratamento aprofundado dos novos modos de isolamento, leia o guia de concorrencia acessivel no Swift 6.2, que mostra como @concurrent e nonisolated(nonsending) interagem com modelos observaveis.

Armadilhas comuns e como evita-las

Apesar da API ser conceitualmente simples, alguns problemas aparecem com frequencia em migracoes reais. Conhece-los antecipadamente economiza horas de debugging em apps com hierarquias de view profundas. Quase todos esses bugs ja apareceram em algum projeto que toquei.

Views nao atualizam apos mudanca em colecao

Se voce muta um elemento dentro de um array de structs sem reatribuir o array, o tracking funciona, desde que a view leia modelo.itens[i].campo. Mas se voce mantem uma referencia local let itens = modelo.itens e itera sobre essa copia, as mudancas nao disparam invalidacao porque a view nunca leu a propriedade rastreada diretamente no corpo. Sempre acesse modelo.x diretamente no body.

Computed properties nao sao rastreadas, mas suas dependencias sim

Propriedades calculadas (var total: Decimal { ... }) nao geram tracking proprio. Porem, se elas leem propriedades armazenadas rastreadas, qualquer view que use a computed property sera reinvalidada quando essas dependencias mudarem. Voce nao precisa de nenhuma anotacao extra para isso funcionar.

Singletons globais perdem rastreamento

Acessar MeuSingleton.shared.valor dentro de uma view funciona, mas o tracking so e estabelecido se o SwiftUI conseguir registrar o acesso durante o calculo do body. Sempre prefira injetar singletons via .environment(MeuSingleton.shared) em vez de acessa-los diretamente. Assim voce ganha rastreamento, testabilidade e previews mais faceis.

Misturar com @Published causa duplicacao

Nao anote propriedades de uma classe @Observable com @Published, o atributo e ignorado e gera ruido. Da mesma forma, nao faca a classe conformar a ObservableObject ao mesmo tempo: escolha um modelo de observacao por classe e mantenha a consistencia em toda a base.

Para modelos que persistem dados, vale combinar @Observable com o SwiftData. Veja como isso funciona em conjunto com heranca de modelos no nosso guia de heranca de modelos no SwiftData, que mostra padroes para queries reativas e migracoes sem perder o tracking.

Perguntas frequentes

Preciso substituir todos os ObservableObject ao migrar para iOS 17 ou iOS 26?

Nao e obrigatorio. ObservableObject continua funcionando no iOS 17 e iOS 26 sem deprecation oficial. A migracao para @Observable e recomendada para novo codigo e para modelos com muitas propriedades, onde o ganho de granularidade no tracking e mais perceptivel, mas pode ser feita gradualmente.

@Observable funciona com structs ou apenas com classes?

Apenas com classes. A macro depende de semantica de referencia para manter um registrar compartilhado entre observadores. Para tipos de valor, continue usando @State diretamente, que ja entrega rastreamento de mudanca via copia-na-escrita.

Qual e a diferenca entre @State e @Bindable em uma view?

@State e responsavel por possuir a instancia, mantem a referencia viva durante o ciclo de vida da view. @Bindable nao armazena nada: ele apenas habilita a sintaxe $modelo.campo para gerar Binding a partir de propriedades de uma instancia recebida de fora.

@Observable e mais rapido que ObservableObject?

Sim, em cenarios tipicos. Como o rastreamento e por propriedade lida, apenas as views que realmente dependem do valor alterado sao reinvalidadas. Em telas com listas longas ou formularios extensos, isso costuma se traduzir em menos chamadas a body e renderizacao mais fluida.

Posso usar @Observable com Combine?

Nao diretamente. @Observable nao expoe um objectWillChange publisher como ObservableObject. Se voce precisa de pipelines Combine reagindo a mudancas, mantenha o modelo como ObservableObject ou use withObservationTracking para integrar manualmente o tracking com seu proprio sink.

Editorial Team
Sobre o Autor Editorial Team

Our team of expert writers and editors.