El desarrollo en el ecosistema de Apple está en constante evolución. Si llevas tiempo trabajando con SwiftUI, seguramente has lidiado con la “sopa de letras” de la gestión de estado: @StateObject, @ObservedObject, @EnvironmentObjecty el siempre presente @Published. Aunque funcionales, estos property wrappers venían con una carga cognitiva y de rendimiento que a menudo complicaba la arquitectura de nuestras apps.
Con la llegada de Swift 5.9 (iOS 17, macOS 14, watchOS 10), Apple introdujo un cambio de paradigma masivo: el marco de trabajo Observation y la macro @Observable.
Este artículo es una guía técnica diseñada para llevarte de cero a experto en el uso de @Observable, explicándote no solo cómo usarlo, sino por qué cambia radicalmente la forma en que desarrollamos para iOS, macOS y watchOS.
Parte 1: ¿Qué es @Observable y por qué lo necesitamos?
Para entender el futuro, primero debemos mirar brevemente al pasado. Antes de Swift 5.9, SwiftUI dependía fuertemente del framework Combine para la gestión de datos reactivos.
El problema del “Viejo Mundo” (ObservableObject)
Cuando usábamos ObservableObject, teníamos que marcar explícitamente cada propiedad que queríamos que la vista “vigilara” con @Published.
El problema fundamental era la granularidad. Si tenías un objeto con 10 propiedades marcadas como @Published, y una vista solo usaba una de ellas, la vista se invalidaba y redibujaba incluso si cambiaba una de las otras 9 propiedades que la vista ni siquiera estaba mostrando. Esto generaba redibujados innecesarios y cuellos de botella en el rendimiento en aplicaciones complejas.
La Solución: La Macro @Observable
@Observable no es un property wrapper tradicional; es una Macro. Las macros en Swift transforman tu código en tiempo de compilación.
Al añadir @Observable antes de una clase, la macro reescribe automáticamente esa clase para conformar al protocolo Observable. Lo revolucionario aquí es el seguimiento preciso de dependencias.
Ventajas Clave:
- Sintaxis Limpia: Adiós a
@Published. Las propiedades son observadas por defecto. - Rendimiento Superior: Las vistas solo se actualizan si cambia una propiedad que la vista leyó específicamente en su cuerpo (
body). - Menos Property Wrappers: Simplifica drásticamente la inyección de dependencias.
Parte 2: Configurando el Entorno en Xcode
Antes de escribir código, asegúrate de tener las herramientas correctas. @Observable requiere:
- Xcode: Versión 15.0 o superior.
- Target: iOS 17+, macOS 14+, watchOS 10+, tvOS 17+ o visionOS.
Si estás manteniendo una app que debe soportar iOS 16 o inferior, no podrás migrar completamente a este sistema todavía (aunque puedes usar código condicional).
Parte 3: Tutorial Práctico – Creando un Modelo de Datos
Vamos a construir un sistema de gestión de un “Perfil de Usuario” que funcione a través de iPhone, Mac y Apple Watch.
Paso 1: Definiendo el Modelo
En el modelo antiguo, haríamos esto:
// MÉTODO ANTIGUO (Deprecado conceptualmente)
class UserProfileOld: ObservableObject {
@Published var name: String = "Dev"
@Published var isPremium: Bool = false
var lastLogin: Date = Date() // No notifica cambios
}Con @Observable, el código se ve así:
import Observation
import Foundation
@Observable
class UserProfile {
var name: String = "Dev"
var isPremium: Bool = false
var lastLogin: Date = Date()
// Propiedades calculadas también son observadas automáticamente
var greeting: String {
return "Hola, \(name)"
}
// Podemos excluir propiedades de la observación si es necesario
@ObservationIgnored var analyticsId: String = "XYZ-123"
init(name: String, isPremium: Bool) {
self.name = name
self.isPremium = isPremium
}
}Nota Técnica: Al compilar, la macro
@Observableinyecta código que implementa el métodoaccess(keyPath:)ywithMutation(keyPath:). Esto conecta las propiedades con el grafo de dependencias de SwiftUI de forma invisible para ti.
Parte 4: Inyección y Uso en las Vistas (iOS/macOS/watchOS)
Aquí es donde la magia ocurre. La forma en que inyectamos los datos cambia sutilmente pero con gran impacto.
1. La “Fuente de la Verdad” (@State)
Antes, @State era solo para tipos de valor (structs, ints, bools). Para clases usábamos @StateObject. Ahora, @State puede gestionar el ciclo de vida de objetos @Observable.
import SwiftUI
struct ContentView: View {
// Ya no necesitamos @StateObject. @State es suficiente.
@State private var user = UserProfile(name: "Ana", isPremium: true)
var body: some View {
VStack {
// SwiftUI detecta que leemos 'user.name'.
// Si 'user.isPremium' cambia, esta vista NO se redibujará. ¡Magia!
Text(user.greeting)
.font(.largeTitle)
EditProfileView(user: user)
}
.padding()
}
}2. Pasando datos a hijos (Binding vs Let)
Si solo necesitas leer el objeto en una vista hija, pásalo como una propiedad normal (let o var). No necesitas @ObservedObject.
struct ReadOnlyView: View {
// Simplemente declaramos el tipo. Sin wrappers.
let user: UserProfile
var body: some View {
Text("Estado: \(user.isPremium ? "Premium" : "Básico")")
}
}3. Creando Bindings (@Bindable)
Este es el punto que más confunde a los desarrolladores al principio. Si necesitamos crear un binding (usar el símbolo $) para controles como TextField, Toggle o Stepper, no podemos usar el objeto tal cual. Necesitamos el wrapper @Bindable.
@Bindable crea enlaces ligeros a las propiedades del objeto observable.
struct EditProfileView: View {
// Usamos @Bindable para poder acceder a los $bindings
@Bindable var user: UserProfile
var body: some View {
Form {
Section("Editar Información") {
// Aquí necesitamos el $, por eso usamos @Bindable arriba
TextField("Nombre", text: $user.name)
Toggle("Suscripción Premium", isOn: $user.isPremium)
}
}
}
}Si intentaras usar var user: UserProfile sin @Bindable, Xcode te daría un error al intentar acceder a $user.name.
Parte 5: Gestión del Entorno (Environment)
El patrón de inyección de dependencias también se ha simplificado. EnvironmentObject (que dependía de tipos dinámicos y causaba crashes si olvidabas inyectarlo) se sustituye por el uso más seguro de .environment.
Inyectando en la raíz (App)
@main
struct MyApp: App {
@State private var user = UserProfile(name: "Carlos", isPremium: false)
var body: some Scene {
WindowGroup {
ContentView()
// Inyectamos el objeto en el entorno
.environment(user)
}
}
}Leyendo en la vista
Para leerlo, usamos el wrapper @Environment, no EnvironmentObject.
struct SettingsView: View {
// Recuperamos el objeto basado en su TIPO
@Environment(UserProfile.self) var user
var body: some View {
if user.isPremium {
Text("Ajustes Avanzados")
} else {
Text("Actualiza a Premium para ver más")
}
}
}Este cambio unifica la forma en que pasamos valores simples (como ColorScheme) y nuestros objetos de datos complejos.
Parte 6: Adaptabilidad Multiplataforma
Una de las grandes promesas de SwiftUI es “Learn once, apply anywhere”. El código de datos que hemos escrito arriba es 100% idéntico para iOS, macOS y watchOS. La diferencia radica en cómo renderizamos la UI.
Ejemplo para watchOS
En un Apple Watch, el espacio es limitado. Podríamos tener una vista específica:
// WatchOS Specific View
struct WatchUserProfileView: View {
@State private var user = UserProfile(name: "Ana", isPremium: true)
var body: some View {
NavigationStack {
List {
// El Toggle se adapta visualmente al estilo de watchOS
Toggle(isOn: $user.isPremium) {
Label("Premium", systemImage: "star.fill")
}
}
.navigationTitle(user.name)
}
}
}Ejemplo para macOS
En macOS, podríamos querer una ventana de ajustes separada:
// macOS Settings View
struct MacSettingsView: View {
@Environment(UserProfile.self) var user
var body: some View {
TabView {
Form {
TextField("Nombre", text: Bindable(user).name)
// Nota: A veces podemos usar Bindable() inline si no queremos el wrapper global
}
.tabItem { Label("General", systemImage: "gear") }
}
.scenePadding()
}
}Truco Pro: Nota el uso de
Bindable(user).nameen el ejemplo de macOS. Si no quieres decorar la propiedad de la vista con@Bindable, puedes crear un bindable “al vuelo” dentro delbody.
Parte 7: Casos Avanzados y Migración
Arrays y Colecciones
Uno de los puntos débiles de ObservableObject era observar cambios dentro de arrays de objetos. Con @Observable, la gestión es más fluida, pero requiere atención.
Si tienes un array de modelos @Observable: var users: [UserProfile]
SwiftUI detectará:
- Si añades o eliminas elementos del array.
- Si cambias una propiedad de uno de los elementos (siempre que la vista esté pintando ese elemento específico).
Migración Progresiva
No necesitas reescribir toda tu app en un día. Puedes mezclar ObservableObject y @Observable.
- Las vistas nuevas pueden usar el nuevo sistema.
- Las vistas antiguas pueden seguir usando
@StateObject.
Sin embargo, no mezcles ambos en la misma clase. Una clase no debe ser @Observable y ObservableObject al mismo tiempo.
Errores Comunes (Gotchas)
- Structs vs Clases:
@Observableestá diseñado para clases (tipos de referencia). Si usas structs, sigue usando@Statesimple. - Ciclos de Retención: Al igual que antes, ten cuidado con los closures dentro de tus clases observables. Usa
[weak self]. - Vistas que no actualizan: Si una vista no se actualiza, verifica que realmente estás leyendo la propiedad en el
body. Si solo lees la propiedad en una función que se llama (pero cuyo resultado no cambia la estructura de la vista), es posible que el sistema de observación no registre la dependencia.
Tabla Resumen: El Antes y El Después
| Concepto | Antes (SwiftUI Legacy) | Ahora (SwiftUI Moderno) |
| Definición | class Model: ObservableObject | @Observable class Model |
| Publicación | @Published var data | var data (Automático) |
| Creación (Dueño) | @StateObject var model = Model() | @State var model = Model() |
| Consumo (Lectura) | @ObservedObject var model | let model: Model |
| Consumo (Binding) | @ObservedObject / @Binding | @Bindable var model |
| Entorno | @EnvironmentObject var model | @Environment(Model.self) var model |
Conclusión
La introducción de @Observable en Swift 5.9 es más que azúcar sintáctico; es una reconstrucción fundamental de cómo fluyen los datos en nuestras aplicaciones. Al eliminar la dependencia explícita de Combine para la capa de vista y aprovechar el poder de las Macros, Apple ha entregado una herramienta que es a la vez más fácil de enseñar a los principiantes y mucho más potente para los expertos preocupados por el rendimiento.
Para desarrolladores trabajando en el ecosistema Apple, adoptar @Observable no es opcional a largo plazo. Es el estándar que definirá el desarrollo en SwiftUI durante los próximos años. Reduce el código repetitivo, elimina errores comunes de invalidación de vistas y hace que nuestras aplicaciones se sientan más rápidas y fluidas.
Ya sea que estés construyendo la próxima gran app para iOS, una utilidad compleja para macOS o una experiencia ligera para watchOS, @Observable es tu nuevo mejor amigo.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










