Si has estado desarrollando aplicaciones para el ecosistema de Apple durante los últimos años, es probable que la gestión del estado haya sido tu principal fuente de dolores de cabeza. SwiftUI, en su concepción original, dependía fuertemente del framework Combine para comunicar cambios de datos a la interfaz de usuario. Palabras clave como ObservableObject, @Published y @StateObject se convirtieron en el pan de cada día.
Sin embargo, con la llegada de Swift 5.9 (iOS 17, macOS 14, watchOS 10), Apple introdujo un cambio tectónico: el framework Observation y la macro @Observable.
Este tutorial no es solo una comparación superficial; es una inmersión técnica en las diferencias arquitectónicas, de rendimiento y de sintaxis entre el “Viejo Mundo” (@Published) y el “Nuevo Mundo” (@Observable). Aprenderás por qué Apple hizo este cambio y cómo puedes aprovecharlo para crear apps más rápidas y limpias en Xcode.
Parte 1: Entendiendo los Contendientes
Para entender las diferencias, primero debemos diseccionar cómo funciona cada sistema bajo el capó.
El Veterano: ObservableObject y @Published
En las primeras versiones de SwiftUI (iOS 13-16), la reactividad se basaba en el protocolo ObservableObject.
Cuando conformamos una clase a este protocolo, el compilador sintetiza un objectWillChange publisher (emisor de eventos). Pero, ¿cómo sabe este emisor cuándo dispararse? Aquí entra @Published.
@Published es un property wrapper. Envuelve tu propiedad y añade un gancho (hook) en el willSet. Cada vez que asignas un nuevo valor a una propiedad marcada con @Published, esta avisa al objeto padre: “¡Oye, voy a cambiar! Avisa a la vista”.
El problema fundamental: La granularidad. El aviso es a nivel de Objeto, no de propiedad.
El Retador: La Macro @Observable
El nuevo sistema utiliza las nuevas Macros de Swift. Cuando anotas una clase con @Observable, no estás usando un property wrapper en tiempo de ejecución. Estás instruyendo al compilador para que reescriba tu clase en tiempo de compilación.
La macro inyecta lógica en los getters y setters de todas las propiedades almacenadas. SwiftUI ya no “escucha” al objeto entero; en su lugar, crea un Grafo de Dependencias preciso. Si una vista lee una propiedad, se suscribe a esapropiedad específica.
Parte 2: Diferencias Críticas de Arquitectura
Aquí es donde entramos en materia técnica. La diferencia entre ambos no es solo sintaxis; es comportamiento.
1. El Problema de la Invalidación Excesiva (Over-invalidation)
Esta es la razón número uno por la que Apple creó @Observable.
Escenario con @Published: Imagina un UserProfileViewModel con dos propiedades:
class UserProfile: ObservableObject {
@Published var username: String = "Dev"
@Published var heartRate: Int = 60 // Cambia 100 veces por minuto
}Y tienes una vista que solo muestra el nombre:
struct NameView: View {
@ObservedObject var model: UserProfile
var body: some View {
Text(model.username)
}
}Lo que sucede: Cada vez que heartRate cambia (aunque la vista no lo use), objectWillChange se dispara. SwiftUI invalida NameView, calcula el body, compara el resultado y se da cuenta de que nada visual cambió. Esto es un desperdicio masivo de ciclos de CPU.
Escenario con @Observable:
@Observable class UserProfile {
var username: String = "Dev"
var heartRate: Int = 60
}Lo que sucede: Cuando NameView se renderiza por primera vez, lee model.username. El sistema de observación registra: “NameView depende de UserProfile.username”. Si heartRate cambia, el sistema mira el grafo de dependencias, ve que nadie está observando heartRate en esta vista, y no hace nada. La vista ni siquiera se entera.
2. Sintaxis y “Boilerplate”
La reducción de ruido visual es drástica. Veamos una comparación directa de código.
El Viejo Estilo (Combine)
import SwiftUI
class SettingsModel: ObservableObject {
@Published var isDarkMode: Bool = false
@Published var volume: Double = 0.5
// Las propiedades calculadas no emiten cambios automáticamente
// a menos que dependan de propiedades @Published
}
struct SettingsView: View {
@StateObject private var model = SettingsModel()
var body: some View {
Toggle("Dark Mode", isOn: $model.isDarkMode)
}
}El Nuevo Estilo (Observation)
import SwiftUI
import Observation // Importante si no importas SwiftUI
@Observable
class SettingsModel {
var isDarkMode: Bool = false
var volume: Double = 0.5
// No hace falta marcar nada como @Published
}
struct SettingsView: View {
// Adiós @StateObject, hola @State
@State private var model = SettingsModel()
var body: some View {
// Enlace directo
Toggle("Dark Mode", isOn: $model.isDarkMode)
}
}3. Inyección de Dependencias (El fin de la “Sopa de Wrappers”)
Con @Published, teníamos reglas estrictas y confusas sobre qué wrapper usar en la vista:
- Creación:
@StateObject(para que el objeto sobreviva a redibujados). - Observación:
@ObservedObject(si alguien más lo creó). - Entorno:
@EnvironmentObject.
Si te equivocabas y usabas @ObservedObject donde debías usar @StateObject, tu modelo se reiniciaba aleatoriamente.
Con @Observable, todo se simplifica:
- Creación: Usa
@State. Sí,@Stateahora gestiona el ciclo de vida de objetos observables. - Observación: Usa
letovarnormales. No necesitas un wrapper. SwiftUI detecta automáticamente que es un objeto observable. - Bindings: Usa
@Bindablesi necesitas el símbolo$(explicaremos esto más adelante). - Entorno: Usa
@Environment(TuClase.self).
Parte 3: Profundizando en las Propiedades Calculadas
Una de las diferencias más sutiles pero poderosas está en cómo se manejan las propiedades calculadas (computed properties).
Con @Published: Si tenías una propiedad calculada que dependía de otras variables, @Published funcionaba bien siempre y cuando las dependencias también fueran @Published. Pero si tu propiedad calculada dependía de algo externo o complejo, a menudo tenías que llamar manualmente a objectWillChange.send().
Con @Observable: El sistema es inteligente. Rastrea las dependencias de forma recursiva.
@Observable class Cart {
var items: [Item] = []
// Esta propiedad es observada automáticamente
var total: Double {
items.reduce(0) { $0 + $1.price }
}
}Si añades un item al array, total cambia. Cualquier vista que muestre total se actualizará. No necesitas código extra.
Parte 4: El papel de @Bindable (El nuevo jugador)
Aquí es donde muchos desarrolladores se confunden al migrar.
En el viejo sistema, @ObservedObject proyectaba un Binding automáticamente (vía $modelo). En el nuevo sistema, como pasamos el objeto como un let o var simple, no tenemos esa proyección automática.
Si necesitas pasar un valor a un TextField o un Toggle (que requieren escritura), necesitas usar el wrapper @Bindable.
Tutorial de uso de @Bindable:
- En la raíz: Creas el objeto con
@State. Accedes al binding con$.
@State private var user = User()
TextField("Nombre", text: $user.name) // Funciona
2. En una vista hija: Recibes el objeto “crudo”.
struct EditView: View {
var user: User // Recibido normal
var body: some View {
// Error: No puedes usar $user
// TextField("Nombre", text: $user.name)
// Solución: Crear un Bindable al vuelo
@Bindable var bUser = user
TextField("Nombre", text: $bUser.name)
}
}También puedes declarar @Bindable var user: User en los argumentos de la vista si siempre vas a necesitar bindings.
Parte 5: Rendimiento y Colecciones
El manejo de colecciones (Arrays de objetos) siempre fue un punto doloroso con ObservableObject. Si tenías un Array de objetos observables, cambiar una propiedad dentro de uno de esos objetos no actualizaba la vista que mostraba la lista, a menos que invalidaras el array manualmente.
Con @Observable, la propagación es mucho más fluida, pero requiere entender que la observación ocurre en el acceso.
Si tienes: var users: [User] (donde User es @Observable)
Y tu vista hace:
ForEach(users) { user in
Text(user.name)
}SwiftUI rastrea:
- El array
users(si añades/borras elementos). - La propiedad
namede cada instanciaUservisible.
Esto permite listas enormemente eficientes donde actualizar el nombre del usuario #50 solo repinta esa celda específica, sin tocar el resto de la lista ni la estructura del contenedor.
Parte 6: Guía de Migración Paso a Paso
¿Tienes una app grande en Xcode con ObservableObject? No entres en pánico. No necesitas reescribirlo todo hoy.
Estrategia de Convivencia
Ambos sistemas pueden coexistir en la misma app, pero no en la misma vista/objeto.
- No mezcles: Un objeto no puede ser
ObservableObjecty@Observablea la vez. - Vistas Híbridas: Una vista puede tener un
@StateObject(viejo) y un@State(nuevo) de dos modelos diferentes. Funcionará.
Pasos para Refactorizar un Modelo
- Cambia la declaración:
- Elimina
: ObservableObject. - Añade
@Observableantes declass. - Importa
Observation.
- Elimina
- Limpia las propiedades:
- Elimina todos los
@Published. Simplemente déjalos comovar.
- Elimina todos los
- Actualiza las Vistas:
- Cambia
@StateObject var model = ...por@State var model = .... - Cambia
@ObservedObject var model: Tipoporlet model: Tipo(ovar). - Si usas bindings (
$model.propiedad), añade@Bindable var modelen el cuerpo de la vista o en la declaración. - Cambia
@EnvironmentObjectpor@Environment(Tipo.self).
- Cambia
Parte 7: ¿Cuándo NO usar @Observable?
A pesar de todas las ventajas, hay un muro insalvable: La compatibilidad.
El framework Observation solo está disponible en:
- iOS 17.0+
- macOS 14.0+
- watchOS 10.0+
- tvOS 17.0+
Si tu cliente o tu empresa requieren soporte para iOS 15 o iOS 16, no puedes usar @Observable (a menos que uses #available y mantengas dos versiones del código, lo cual no suele ser práctico para la capa de modelo).
En este caso, @Published y ObservableObject siguen siendo herramientas perfectamente válidas, estables y soportadas por Apple. No están “deprecadas” en el sentido de que vayan a desaparecer pronto, simplemente son la tecnología “legacy”.
Parte 8: Resumen Técnico
Para cerrar este tutorial, aquí tienes una tabla de referencia rápida para tener a mano en tu escritorio mientras programas en Xcode.
| Característica | ObservableObject (@Published) | @Observable (Macro) |
| Mecanismo | Combine (Publisher/Subscriber) | Swift Runtime & Macros |
| Granularidad | Objeto entero (Cualquier cambio notifica) | Propiedad individual (Solo lo usado notifica) |
| Declaración | class A: ObservableObject | @Observable class A |
| Marcar Propiedades | @Published var x | var x (Automático) |
| Ignorar Propiedades | var x (Sin @Published) | @ObservationIgnored var x |
| En la Vista (Dueño) | @StateObject | @State |
| En la Vista (Lector) | @ObservedObject | let / var |
| Bindings | Automático ($objeto.prop) | Manual (@Bindable o Bindable()) |
| Requisito Mínimo | iOS 13 | iOS 17 |
Conclusión
La transición de @Published a @Observable representa la madurez de SwiftUI. Apple ha eliminado la dependencia de Combine para la capa de la vista, reconociendo que el modelo reactivo de flujos (Streams) no siempre es el mejor modelo para el estado de la interfaz (State).
@Observable es más “Swift-nativo”. Se siente menos como una librería añadida y más como una característica intrínseca del lenguaje. Reduce el código repetitivo, elimina errores comunes de rendimiento y simplifica la enseñanza de SwiftUI a nuevos desarrolladores.
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










