@StateObject é um Property Wrapper no SwiftUI para criar e possuir uma instância de ObservableObject diretamente em uma view. O SwiftUI garante que o objeto seja inicializado uma vez por ciclo de vida da view e não seja recriado em renderizações subsequentes. De acordo com a Documentação para Desenvolvedores da Apple (2025), o @StateObject é recomendado para views raiz que criam uma fonte de dados. @StateObject é a escolha certa para possuir um ObservableObject na hierarquia do SwiftUI.
Pontos principais
@StateObject é um Property Wrapper introduzido no SwiftUI 2.0 (iOS 14) que combina as capacidades de @ObservedObject e @State. Como @ObservedObject, ele assina as alterações de ObservableObject. Como @State, ele garante que os dados sobrevivam a inicializações repetidas da estrutura da view. O @StateObject cria o objeto uma vez quando a view aparece pela primeira vez e o armazena no heap do SwiftUI.
Antes do @StateObject, os desenvolvedores usavam @ObservedObject para todos os ObservableObjects, incluindo os criados em views. Isso levava a perdas frequentes de dados quando a view pai era atualizada, fazendo com que a estrutura da view fosse recriada e levando a instância do @ObservedObject junto. O @StateObject resolveu isso adicionando uma garantia de estabilidade.
A regra principal: @StateObject é usado na view que cria o objeto no inicializador padrão (let model = ViewModel()). As views filhas que recebem este objeto usam @ObservedObject. Essa separação garante uma única fonte da verdade em toda a hierarquia.
O SwiftUI gerencia o ciclo de vida do @StateObject através de um gerenciador de armazenamento semelhante ao @State. Quando a view aparece pela primeira vez, o SwiftUI aloca memória para o objeto e o armazena em uma área persistente. Em renderizações subsequentes (chamadas de body), o objeto não é recriado—a instância existente é usada. O objeto vive enquanto a view estiver na hierarquia.
Quando a view é removida da hierarquia, o SwiftUI destrói o @StateObject, chamando deinit. Quando a view é adicionada novamente à hierarquia, uma nova instância é criada. Isso é importante ao projetar: se precisar preservar dados entre remoções de view, use uma camada de serviço (singleton ou DI) ou @AppStorage para persistência.
class TimerViewModel: ObservableObject {
@Published var seconds: Int = 0
private var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
}
deinit {
timer?.invalidate()
}
}
struct TimerView: View {
@StateObject var viewModel = TimerViewModel()
var body: some View {
Text("\(viewModel.seconds)s")
.onAppear { viewModel.start() }
}
}
No exemplo, TimerViewModel é criado via @StateObject e vive enquanto TimerView estiver na tela. O temporizador inicia em onAppear e para em deinit. Se @ObservedObject fosse usado, cada renderização de TimerView criaria um novo TimerViewModel com segundos = 0, e o temporizador nunca funcionaria corretamente. O @StateObject garante que o viewModel seja único e estável.
A escolha entre @StateObject e @ObservedObject depende de quem possui o objeto. Se a view cria o objeto—@StateObject. Se a view recebe um objeto pronto—@ObservedObject. Esta regra é tão importante que o Xcode exibe um aviso ao usar @StateObject em uma view filha que recebe o objeto através de um inicializador.
| Situação | Wrapper recomendado |
|---|---|
| A view cria modelo via ViewModel() | @StateObject |
| A view recebe modelo do pai | @ObservedObject |
| O modelo é usado em uma view | @StateObject |
| O modelo é passado via Environment | @EnvironmentObject |
| O modelo é necessário para prévias | @ObservedObject + mock |
Na prática, no início de um projeto, @StateObject é frequentemente usado na view raiz e @ObservedObject em todas as views filhas. À medida que o aplicativo cresce, algumas instâncias de @StateObject podem ser substituídas por @EnvironmentObject para simplificar a hierarquia. No entanto, o @StateObject continua sendo a melhor escolha para telas modulares com lógica própria.
O primeiro padrão—MVVM com @StateObject. O ViewModel como ObservableObject é criado na view via @StateObject. O ViewModel contém propriedades @Published e lógica de negócios. A view assina as alterações e atualiza a interface. Essa abordagem fornece isolamento testável: o ViewModel pode ser testado sem UI criando uma instância diretamente.
O segundo padrão—@StateObject com dependências. Se o ViewModel exigir serviços, use inicialização com parâmetros. Por exemplo, @StateObject var viewModel = UserViewModel(api: APIClient.shared). No entanto, tenha cuidado: os parâmetros são calculados em cada renderização de body, mas o objeto é criado apenas uma vez. O SwiftUI ignora inicializações subsequentes de @StateObject.
O terceiro padrão—@StateObjects aninhados. No SwiftUI, você pode ter vários @StateObjects em uma view, mas isso raramente se justifica. Normalmente, um @StateObject gerencia todo o conjunto de dados da view. Se a lógica se tornar muito complexa, divida-a em uma composição de serviços @ObservedObject dentro de um @StateObject.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
No exemplo, AppView cria dois @StateObjects: NavigationRouter para gerenciar navegação e AuthViewModel para autenticação. Ambos os objetos são injetados no Environment via environmentObject. Qualquer view filha pode acessá-los através de @EnvironmentObject sem passar pela cadeia de inicializadores.
@StateObject suporta inicialização com qualquer parâmetro, mas com uma ressalva importante: o inicializador é chamado apenas uma vez. Em renderizações subsequentes de body, novos valores nos parâmetros são ignorados. Isso significa que se você passar @State var id: Int = 5 para @StateObject var vm = ViewModel(id: id), quando id mudar, o ViewModel não receberá o novo valor.
Para resolver este problema, use onReceive ou onAppear para sincronização. Assine as alterações de parâmetros dentro do ViewModel via Combine ou passe parâmetros através do método .onChange(of:) no nível da view. Uma alternativa é usar @ObservedObject em vez de @StateObject se o objeto deve responder dinamicamente a alterações externas.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
A abordagem correta: DetailView recebe itemId como uma propriedade let (passada através do inicializador da estrutura), e @StateObject cria DetailViewModel sem parâmetros. Em onAppear, o método load(id:) é chamado para carregar dados para o ID recebido. Isso garante que o ViewModel seja criado pelo mecanismo @StateObject, mas os dados são carregados em cada aparição da view com o ID atual.
O principal erro—usar @StateObject em views filhas que recebem o objeto de um pai. Se ParentView cria @StateObject model, e ChildView declara @StateObject var model: ModelType (com um parâmetro padrão), a ChildView criará sua própria instância independente. Os objetos pai e filho não estarão conectados, e as alterações em um não refletirão no outro.
O segundo erro—colocar @StateObject em List ou ForEach. Cada elemento da lista cria seu próprio @StateObject, levando a múltiplas instâncias independentes. Para listas, a abordagem correta é passar um único ObservableObject para todos os elementos via @ObservedObject ou usar estruturas Identifiable com @State dentro de List.
O terceiro problema—falta de limpeza em deinit. O @StateObject vive por todo o ciclo de vida da view. Se o objeto cria temporizadores, assinaturas Combine ou solicitações de rede, o deinit deve cancelá-los. Caso contrário, vazamentos de memória e trabalho contínuo em segundo plano após o fechamento da tela são inevitáveis. Sempre use um armazenamento Cancellable do Combine ou invalide temporizadores em deinit.
Perguntas frequentes
@StateObject foi adicionado no SwiftUI 2.0 na WWDC 2020 junto com iOS 14, macOS 11, watchOS 7 e tvOS 14. Antes disso, o @ObservedObject era a única maneira de trabalhar com ObservableObject, o que frequentemente levava a bugs de perda de dados.
Não, @StateObject não suporta tipos Optional. O objeto deve ser inicializado na declaração. Se precisar de um objeto opcional, use @ObservedObject ou @EnvironmentObject com um tipo opcional.
Adicione print(#function) ao inicializador e deinit do ObservableObject. Se init não for chamado em renderizações—@StateObject está funcionando corretamente. Se init for chamado toda vez—substitua @ObservedObject por @StateObject.
Sim, @StateObject funciona em views SwiftUI incorporadas no UIKit via UIHostingController. O ciclo de vida do objeto está vinculado à view SwiftUI, não ao UIViewController. Se a view SwiftUI for substituída, o @StateObject é destruído.
Vários @StateObjects pequenos com responsabilidades separadas. Isso melhora a testabilidade, reutilização e desempenho—quando um objeto muda, apenas as partes subscritas da interface são redesenhadas, não a view inteira.
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