@StateObject: qué es, diferencia con @ObservedObject y ejemplos

Autor: IT Sectr Publicado: 2026-06-19 Tiempo de lectura: 7 min

@StateObject es un Property Wrapper en SwiftUI para crear y poseer una instancia de ObservableObject directamente en una vista. SwiftUI garantiza que el objeto se inicialice una vez por ciclo de vida de la vista y no se vuelva a crear en renderizaciones posteriores. Según la Documentación para Desarrolladores de Apple (2025), se recomienda @StateObject para vistas raíz que crean una fuente de datos. @StateObject es la opción correcta para poseer un ObservableObject en la jerarquía de SwiftUI.

Puntos clave

  • @StateObject — Property Wrapper para crear y poseer ObservableObject en una vista
  • Única instancia — el objeto se crea una vez y no se recrea en renderizaciones
  • Fuente de verdad — @StateObject garantiza la estabilidad de datos para toda la jerarquía
  • Diferencia de @ObservedObject — @ObservedObject no posee el objeto y puede perderlo
  • Vistas raíz — @StateObject se usa en la vista que crea el objeto

¿Qué es @StateObject en SwiftUI?

@StateObject es un Property Wrapper introducido en SwiftUI 2.0 (iOS 14) que combina las capacidades de @ObservedObject y @State. Al igual que @ObservedObject, se suscribe a los cambios de ObservableObject. Como @State, garantiza que los datos sobrevivan a inicializaciones repetidas de la estructura de la vista. @StateObject crea el objeto una vez cuando la vista aparece por primera vez y lo almacena en el heap de SwiftUI.

Antes de @StateObject, los desarrolladores usaban @ObservedObject para todos los ObservableObjects, incluidos los creados en vistas. Esto provocaba pérdidas frecuentes de datos cuando la vista padre se actualizaba, lo que hacía que la estructura de la vista se recreara y se llevara consigo la instancia de @ObservedObject. @StateObject resolvió esto añadiendo una garantía de estabilidad.

La regla principal: @StateObject se usa en la vista que crea el objeto en el inicializador por defecto (let model = ViewModel()). Las vistas hijas que reciben este objeto usan @ObservedObject. Esta separación garantiza una única fuente de verdad en toda la jerarquía.

Ciclo de vida de @StateObject

SwiftUI gestiona el ciclo de vida de @StateObject a través de un gestor de almacenamiento similar a @State. Cuando la vista aparece por primera vez, SwiftUI asigna memoria para el objeto y lo almacena en un área persistente. En renderizaciones posteriores (llamadas a body), el objeto no se recrea—se usa la instancia existente. El objeto vive mientras la vista esté en la jerarquía.

Cuando la vista se elimina de la jerarquía, SwiftUI destruye el @StateObject, llamando a deinit. Cuando la vista se vuelve a añadir a la jerarquía, se crea una nueva instancia. Esto es importante al diseñar: si necesita conservar datos entre eliminaciones de vistas, use una capa de servicio (singleton o DI) o @AppStorage para la persistencia.

swift
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() }
    }
}

En el ejemplo, TimerViewModel se crea mediante @StateObject y vive mientras TimerView esté en pantalla. El temporizador se inicia en onAppear y se detiene en deinit. Si se usara @ObservedObject, cada renderización de TimerView crearía un nuevo TimerViewModel con segundos = 0, y el temporizador nunca funcionaría correctamente. @StateObject garantiza que el viewModel sea único y estable.

@StateObject vs @ObservedObject: comparación

La elección entre @StateObject y @ObservedObject depende de quién posee el objeto. Si la vista crea el objeto—@StateObject. Si la vista recibe un objeto ya creado—@ObservedObject. Esta regla es tan importante que Xcode muestra una advertencia al usar @StateObject en una vista hija que recibe el objeto a través de un inicializador.

SituaciónWrapper recomendado
La vista crea el modelo mediante ViewModel()@StateObject
La vista recibe el modelo del padre@ObservedObject
El modelo se usa en una sola vista@StateObject
El modelo se pasa a través de Environment@EnvironmentObject
El modelo se necesita para previsualizaciones@ObservedObject + mock

En la práctica, al inicio de un proyecto, a menudo se usa @StateObject en la vista raíz y @ObservedObject en todas las vistas hijas. A medida que la aplicación crece, algunas instancias de @StateObject pueden reemplazarse por @EnvironmentObject para simplificar la jerarquía. Sin embargo, @StateObject sigue siendo la mejor opción para pantallas modulares con lógica propia.

Patrones de uso de @StateObject

El primer patrón—MVVM con @StateObject. El ViewModel como ObservableObject se crea en la vista mediante @StateObject. El ViewModel contiene propiedades @Published y lógica de negocio. La vista se suscribe a los cambios y actualiza la interfaz. Este enfoque proporciona un aislamiento comprobable: el ViewModel se puede probar sin interfaz de usuario creando una instancia directamente.

El segundo patrón—@StateObject con dependencias. Si el ViewModel requiere servicios, use la inicialización con parámetros. Por ejemplo, @StateObject var viewModel = UserViewModel(api: APIClient.shared). Sin embargo, tenga cuidado: los parámetros se calculan en cada renderización de body, pero el objeto se crea solo una vez. SwiftUI ignora las inicializaciones posteriores de @StateObject.

El tercer patrón—@StateObject anidados. En SwiftUI, puede tener varios @StateObject en una vista, pero esto rara vez se justifica. Normalmente un @StateObject maneja todo el conjunto de datos de la vista. Si la lógica se vuelve demasiado compleja, divídala en una composición de servicios @ObservedObject dentro de un @StateObject.

swift
struct AppView: View {
    @StateObject var router = NavigationRouter()
    @StateObject var auth = AuthViewModel()

    var body: some View {
        ContentView()
            .environmentObject(router)
            .environmentObject(auth)
    }
}

En el ejemplo, AppView crea dos @StateObject: NavigationRouter para gestionar la navegación y AuthViewModel para la autenticación. Ambos objetos se inyectan en el Environment a través de environmentObject. Cualquier vista hija puede acceder a ellos mediante @EnvironmentObject sin pasar por la cadena de inicializadores.

@StateObject e inicialización con parámetros

@StateObject admite la inicialización con cualquier parámetro, pero con una salvedad importante: el inicializador se llama solo una vez. En renderizaciones posteriores de body, los nuevos valores de los parámetros se ignoran. Esto significa que si pasa @State var id: Int = 5 a @StateObject var vm = ViewModel(id: id), cuando id cambie, el ViewModel no recibirá el nuevo valor.

Para resolver este problema, use onReceive u onAppear para la sincronización. Suscríbase a los cambios de parámetros dentro del ViewModel a través de Combine o pase parámetros mediante el método .onChange(of:) a nivel de vista. Una alternativa es usar @ObservedObject en lugar de @StateObject si el objeto debe responder dinámicamente a cambios externos.

swift
struct DetailView: View {
    let itemId: Int
    @StateObject var viewModel = DetailViewModel()

    var body: some View {
        Text(viewModel.title)
            .onAppear { viewModel.load(id: itemId) }
    }
}

El enfoque correcto: DetailView recibe itemId como una propiedad let (pasada a través del inicializador de la estructura), y @StateObject crea DetailViewModel sin parámetros. En onAppear, se llama al método load(id:) para cargar datos para el ID recibido. Esto garantiza que el ViewModel sea creado por el mecanismo @StateObject, pero los datos se cargan en cada aparición de la vista con el ID actual.

Errores comunes con @StateObject

El principal error—usar @StateObject en vistas hijas que reciben el objeto de un padre. Si ParentView crea @StateObject model, y ChildView declara @StateObject var model: ModelType (con un parámetro por defecto), ChildView creará su propia instancia independiente. Los objetos padre e hijo no estarán conectados, y los cambios en uno no se reflejarán en el otro.

El segundo error—colocar @StateObject en List o ForEach. Cada elemento de la lista crea su propio @StateObject, lo que genera múltiples instancias independientes. Para listas, el enfoque correcto es pasar un único ObservableObject a todos los elementos mediante @ObservedObject o usar estructuras Identifiable con @State dentro de List.

El tercer problema—falta de limpieza en deinit. @StateObject vive durante todo el ciclo de vida de la vista. Si el objeto crea temporizadores, suscripciones Combine o solicitudes de red, deinit debe cancelarlos. De lo contrario, las pérdidas de memoria y el trabajo en segundo plano continuo después del cierre de la pantalla son inevitables. Use siempre un almacén Cancellable de Combine o invalide los temporizadores en deinit.

Preguntas frecuentes

¿Cuándo se introdujo @StateObject en SwiftUI?

@StateObject se agregó en SwiftUI 2.0 en la WWDC 2020 junto con iOS 14, macOS 11, watchOS 7 y tvOS 14. Antes de eso, @ObservedObject era la única forma de trabajar con ObservableObject, lo que a menudo provocaba errores de pérdida de datos.

¿Puede @StateObject ser opcional?

No, @StateObject no admite tipos Optional. El objeto debe inicializarse en la declaración. Si necesita un objeto opcional, use @ObservedObject o @EnvironmentObject con un tipo opcional.

¿Cómo verificar que @StateObject se crea solo una vez?

Agregue print(#function) al inicializador y deinit del ObservableObject. Si init no se llama en renderizaciones posteriores—@StateObject funciona correctamente. Si init se llama cada vez—reemplace @ObservedObject por @StateObject.

¿Se puede usar @StateObject con UIKit mediante UIHostingController?

Sí, @StateObject funciona en vistas de SwiftUI incrustadas en UIKit mediante UIHostingController. El ciclo de vida del objeto está vinculado a la vista de SwiftUI, no al UIViewController. Si la vista de SwiftUI se reemplaza, el @StateObject se destruye.

¿Qué es mejor: un @StateObject con un ViewModel grande o varios pequeños?

Varios @StateObjects pequeños con responsabilidades separadas. Esto mejora la comprobabilidad, la reutilización y el rendimiento—cuando un objeto cambia, solo se vuelven a dibujar las partes suscritas de la interfaz, no toda la vista.

Resumen

  • @StateObject — Property Wrapper para crear y poseer ObservableObject en una vista
  • Única instancia — el objeto no se recrea en renderizaciones posteriores de body
  • Fuente de verdad — @StateObject en la vista raíz garantiza la estabilidad de datos para la jerarquía
  • Regla de selección — @StateObject para crear, @ObservedObject para recibir un objeto ya creado
  • Inicialización — los parámetros en @StateObject se calculan una vez, las actualizaciones no se rastrean
  • Deinit — limpieza obligatoria de temporizadores y suscripciones en el deinit del ObservableObject
  • iOS 14+ — @StateObject está disponible desde iOS 14, macOS 11, watchOS 7, tvOS 14

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también