Programación en Swift y SwiftUI para iOS Developers

@Observable vs ObservableObject en SwiftUI

Para cualquier iOS Developer, la gestión del estado es, sin lugar a dudas, el corazón y el cerebro de cualquier aplicación. Desde el lanzamiento de SwiftUI, Apple nos introdujo a un paradigma reactivo donde la interfaz de usuario es una función directa de su estado. Durante años, el estándar de oro en la programación Swift para manejar estados complejos fue el protocolo ObservableObject.

Sin embargo, con la llegada de Swift 5.9, Xcode 15 y el framework Observation, Apple introdujo la macro @Observable, cambiando las reglas del juego para siempre.

En este tutorial exhaustivo, profundizaremos en qué son, cómo funcionan, y cuáles son las diferencias y semejanzas entre @Observable y ObservableObject en SwiftUI para el desarrollo de aplicaciones multiplataforma en iOS, macOS y watchOS.


1. El Paradigma del Estado en la Programación Swift

Antes de sumergirnos en el código, es vital entender el problema que ambas herramientas intentan resolver. En SwiftUI, las vistas (Views) son estructuras (structs), lo que significa que son tipos de valor (value types) inmutables. Cuando necesitas que tu interfaz reaccione a cambios en datos complejos que viven más allá del ciclo de vida de una sola vista —como el perfil de un usuario, el carrito de compras o la lógica de negocio (ViewModels)— necesitas clases (reference types).

Tanto ObservableObject como @Observable son los puentes que permiten a las vistas de SwiftUI “observar” estas clases y redibujarse (renderizarse) automáticamente cuando los datos cambian.


2. ¿Qué es ObservableObject? (El Enfoque Clásico)

Introducido en la primera versión de SwiftUI (iOS 13, macOS 10.15, watchOS 6), ObservableObject es un protocolo que pertenece al framework Combine.

¿Cómo funciona?

Cuando una clase conforma el protocolo ObservableObject, Swift le proporciona automáticamente un publicador (publisher) oculto llamado objectWillChange. Para que SwiftUI sepa qué propiedades específicas deben disparar una actualización de la interfaz, el iOS Developer debe marcarlas explícitamente con el Property Wrapper @Published.

Ejemplo en Swift con ObservableObject:

import SwiftUI
import Combine

// 1. Conformar el protocolo ObservableObject
class UserViewModel: ObservableObject {
    // 2. Usar @Published para notificar cambios
    @Published var username: String = "iOS_Dev_123"
    @Published var followerCount: Int = 0
    
    // Propiedad normal: los cambios aquí NO actualizarán la UI
    var sessionID: String = UUID().uuidString 
    
    func addFollower() {
        followerCount += 1
    }
}

struct UserProfileView: View {
    // 3. Instanciar usando @StateObject o @ObservedObject
    @StateObject private var viewModel = UserViewModel()
    
    var body: some View {
        VStack {
            Text("Usuario: \(viewModel.username)")
            Text("Seguidores: \(viewModel.followerCount)")
            Button("Nuevo Seguidor") {
                viewModel.addFollower()
            }
        }
    }
}

Ventajas Históricas:

  • Permitió la implementación limpia del patrón MVVM (Model-View-ViewModel) en SwiftUI.
  • Compatible con todas las versiones de SwiftUI desde su inicio.
  • Integración nativa y profunda con la programación reactiva de Combine.

3. ¿Qué es @Observable? (El Presente y Futuro)

Presentado en la WWDC23 (para iOS 17, macOS 14 y watchOS 10), @Observable no es un protocolo, sino una Macro de Swift. Utiliza el nuevo framework Observation, el cual está escrito enteramente en Swift puro y no depende de Combine.

¿Cómo funciona?

Cuando añades @Observable antes de la declaración de tu clase, Xcode y el compilador de Swift expanden esa macro en tiempo de compilación para inyectar un sistema de seguimiento (tracking) altamente eficiente. Ya no necesitas declarar qué propiedades actualizarán la UI; por defecto, todas las propiedades almacenadas son observables.

SwiftUI registra automáticamente qué propiedades se “leen” dentro del body de una vista y solo redibujará esa vista si esas propiedades específicas cambian.

Ejemplo en Swift con @Observable:

import SwiftUI
import Observation // Se importa automáticamente con SwiftUI en Xcode 15+

// 1. Añadir la macro @Observable
@Observable 
class UserViewModel {
    // 2. Las propiedades son observables por defecto. ¡Adiós @Published!
    var username: String = "iOS_Dev_123"
    var followerCount: Int = 0
    
    // Si NO quieres que una propiedad sea observable, usas @ObservationIgnored
    @ObservationIgnored var sessionID: String = UUID().uuidString 
    
    func addFollower() {
        followerCount += 1
    }
}

struct UserProfileView: View {
    // 3. Instanciar usando simplemente @State
    @State private var viewModel = UserViewModel()
    
    var body: some View {
        VStack {
            Text("Usuario: \(viewModel.username)")
            Text("Seguidores: \(viewModel.followerCount)")
            Button("Nuevo Seguidor") {
                viewModel.addFollower()
            }
        }
    }
}

4. Semejanzas entre @Observable y ObservableObject en SwiftUI

A pesar de sus diferencias arquitectónicas, para un iOS Developer que migra de uno a otro, los conceptos fundacionales permanecen intactos:

  1. Gestión de Tipos de Referencia: Ambos se utilizan exclusivamente en clases (class). No puedes usarlos en structs o enums.
  2. Propósito Principal: Ambos notifican a SwiftUI cuándo debe invalidar y recalcular el body de una vista debido a un cambio en los datos.
  3. Soporte Multiplataforma: Ambos funcionan a la perfección en el ecosistema completo de Apple, permitiendo compartir la misma lógica de negocio (ViewModel) entre tus targets de Xcode para iOS, macOS, tvOS, visionOS y watchOS.
  4. Patrones de Arquitectura: Ambos facilitan arquitecturas como MVVM, permitiendo mantener la lógica de negocio y las llamadas a red separadas del código visual de SwiftUI.

5. Diferencias Clave: La Batalla Definitiva

Aquí es donde radica el verdadero valor de este tutorial. Comprender estas diferencias es lo que distingue a un desarrollador junior de un iOS Developer Senior en la programación Swift moderna.

5.1. Rendimiento y Granularidad (La Gran Mejora)

La diferencia más crítica entre @Observable y ObservableObject en SwiftUI es cómo y cuándo deciden actualizar las vistas.

  • El problema de ObservableObject: Cuando cualquier propiedad @Published cambia, el objectWillChange.send() se emite. SwiftUI invalida cualquier vista que esté observando ese objeto, independientemente de si la vista usa esa propiedad en particular o no. Esto a menudo causa renderizados innecesarios y caídas en los cuadros por segundo (FPS) en vistas complejas.
  • La magia de @Observable: Utiliza un seguimiento de acceso (Access Tracking). Si tu clase tiene 20 propiedades, pero la vista solo lee username, la vista solo se redibujará cuando username cambie. Si followerCount cambia, la vista ignorará el evento porque sabe que no depende de ese dato. Esto ofrece un rendimiento granular óptimo sin trabajo adicional por parte del desarrollador.

5.2. Dependencia de Frameworks

  • ObservableObject requiere importar Combine. Es un puente entre el mundo de la programación reactiva (Publishers/Subscribers) y SwiftUI.
  • @Observable pertenece al framework Observation, que es parte de la librería estándar de Swift. Esto hace que el código sea más puro, portátil y que compile más rápido en Xcode.

5.3. Sintaxis y “Boilerplate”

La programación Swift busca constantemente ser más limpia y legible:

  • ObservableObject: Requiere conformar el protocolo explícitamente y añadir la etiqueta @Published a cada variable que desees observar. Si olvidas un @Published, pasarás horas depurando por qué tu UI no se actualiza.
  • @Observable: Al ser una macro de Swift, se coloca una vez encima de la clase. Asume inteligentemente que quieres observar todo. Si quieres excluir algo (por rendimiento o lógica), usas @ObservationIgnored. El código es significativamente más limpio.

5.4. El Manejo de Arrays y Colecciones

Históricamente, los arrays en ObservableObject eran un dolor de cabeza. Si tenías un @Published var users: [User] (donde User es una clase), agregar un usuario a la matriz actualizaba la UI, pero cambiar una propiedad dentro de un usuario existente (ej. users[0].name = "Nuevo") no disparaba una actualización en SwiftUI.
Con @Observable, el seguimiento de propiedades profundas e iteraciones funciona de forma mucho más natural y predecible.


6. Evolución de los Property Wrappers en SwiftUI

Al cambiar el motor de observación, la forma en que inyectamos dependencias y pasamos datos entre vistas en Xcode también cambió radicalmente. Si estás dando el salto a @Observable, debes aprender el nuevo mapeo de Property Wrappers.

De @StateObject a @State

Antes, para instanciar (crear) un ObservableObject y asegurar que no se destruyera en los redibujados de la vista, usábamos @StateObject.
Ahora, con la macro @Observable, SwiftUI unifica el concepto: usas @State tanto para valores simples (String, Int) como para tus clases observables.

// ANTES (ObservableObject)
@StateObject var viewModel = MyViewModel()

// AHORA (@Observable)
@State var viewModel = MyViewModel()

De @ObservedObject a Nada (o @Bindable)

Antes, para pasar un objeto existente de una vista padre a una vista hija, usábamos @ObservedObject.
Ahora, ¡simplemente pasas la propiedad usando un let o var normal! Como el tracking ocurre internamente, SwiftUI sabe si debe actualizar la vista.

// ANTES
struct ChildView: View {
    @ObservedObject var viewModel: MyViewModel
    // ...
}

// AHORA (Modo Lectura)
struct ChildView: View {
    let viewModel: MyViewModel 
    // ...
}

Excepción importante: Si la vista hija necesita modificar directamente los valores (por ejemplo, pasarlo a un TextField), debes usar @Bindable para crear las conexiones bidireccionales de lectura/escritura (Bindings).

// AHORA (Modo Lectura y Escritura)
struct ChildView: View {
    @Bindable var viewModel: MyViewModel
    
    var body: some View {
        TextField("Nombre", text: $viewModel.username)
    }
}

De @EnvironmentObject a @Environment

El entorno global de SwiftUI es fundamental para datos como la sesión del usuario o temas visuales en aplicaciones de iOS, macOS y watchOS.
Antes, inyectabas con .environmentObject() y leías con @EnvironmentObject.
Ahora, inyectas con .environment() y leías de forma más segura con @Environment.

// ANTES
// Inyección: ContentView().environmentObject(themeManager)
@EnvironmentObject var themeManager: ThemeManager

// AHORA
// Inyección: ContentView().environment(themeManager)
@Environment(ThemeManager.self) private var themeManager

7. ¿Cuándo usar cuál en tus Proyectos de Xcode?

Como iOS Developer, tomar decisiones arquitectónicas es tu día a día. Aunque @Observable es objetivamente superior, la elección depende del contexto de tu proyecto.

Usa @Observable (El Nuevo Estándar) si:

  1. Tu proyecto es nuevo y puedes permitirte establecer como requisito mínimo de despliegue iOS 17, macOS 14 y watchOS 10.
  2. Estás experimentando graves problemas de rendimiento por culpa de re-renderizados masivos causados por ObservableObject en vistas complejas.
  3. Deseas un código más limpio y menos propenso a errores tipográficos.

Mantén ObservableObject (El Clásico Confiable) si:

  1. Tienes una aplicación existente (Legacy) que debe soportar iOS 16, 15 o versiones anteriores. @Observable no es compatible con versiones anteriores (backward compatible).
  2. Tu lógica de negocio depende fuertemente de operadores complejos de Combine (como debounce, combineLatest, flatMap). Aunque puedes mezclar Observation y Combine, migrar ecosistemas altamente reactivos requiere tiempo y cuidado.
  3. Estás desarrollando una librería (Package) de código abierto para la programación Swift y deseas maximizar la compatibilidad con proyectos de otros desarrolladores.

8. Migración Paso a Paso: Un Caso Práctico

Si decides modernizar tu aplicación en Xcode, aquí tienes un resumen del flujo de trabajo de migración:

  1. Reemplaza la conformidad del protocolo : ObservableObject por la macro @Observable encima de la clase.
  2. Elimina import Combine si la clase ya no lo necesita.
  3. Elimina todas las etiquetas @Published de tus propiedades.
  4. Identifica las vistas que crean la instancia y cambia @StateObject por @State.
  5. En las vistas hijas que solo leen datos, elimina @ObservedObject y déjalo como una variable normal (let).
  6. En las vistas hijas que requieren Bindings ($), cambia @ObservedObject por @Bindable.
  7. Actualiza todas las referencias del entorno de @EnvironmentObject a @Environment(Type.self).
  8. Compila y disfruta de un aumento inmediato en el rendimiento de los cuadros por segundo de tu app.

Conclusión

El ecosistema de SwiftUI está madurando a un ritmo vertiginoso. Para un iOS Developer actual, dominar la programación Swift significa entender no solo la sintaxis, sino cómo los frameworks manejan la memoria y los ciclos de renderizado por debajo.

Entender la convivencia y las diferencias entre @Observable y ObservableObject en SwiftUI te permitirá tomar decisiones arquitectónicas más inteligentes. Mientras que ObservableObject nos acompañará durante varios años por cuestiones de retrocompatibilidad, la macro @Observable es, indiscutiblemente, el futuro brillante y eficiente del desarrollo en el ecosistema de Xcode para iOS, macOS y watchOS.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Cómo crear un botón con una imagen en SwiftUI

Next Article

Confirmation Dialog en SwiftUI

Related Posts