Programación en Swift y SwiftUI para iOS Developers

@ObservedObject vs @StateObject en SwiftUI

El desarrollo de aplicaciones en el ecosistema de Apple ha experimentado una transformación sin precedentes. Atrás quedaron los días en los que pasábamos horas enlazando Storyboards e IBOutlets en Xcode. Hoy, el estándar de oro es SwiftUI, un framework declarativo que ha cambiado por completo las reglas del juego. Sin embargo, con esta nueva forma de construir interfaces visuales surge uno de los mayores dolores de cabeza para cualquier iOS Developer: la gestión del estado y el ciclo de vida de los datos.

En el corazón de la programación Swift moderna y reactiva se encuentran los envoltorios de propiedades (Property Wrappers). Si bien @State y @Binding son fantásticos para valores simples (tipos de valor como String o Int), cuando nuestras aplicaciones crecen y necesitamos usar arquitecturas más robustas con clases (tipos de referencia), entramos en el territorio de ObservableObject.

En este extenso tutorial, vamos a desglosar a fondo qué son @ObservedObject y @StateObject en SwiftUI, sus semejanzas, sus diferencias críticas, y cómo dominar su uso en Xcode para construir aplicaciones impecables en iOS, macOS y watchOS.


1. El Fundamento: Entendiendo ObservableObject

Antes de sumergirnos en la diferencia entre @ObservedObject y @StateObject en SwiftUI, debemos entender la base sobre la que ambos operan: el protocolo ObservableObject.

En la programación Swift, las vistas en SwiftUI son structs (tipos de valor). Esto significa que son increíblemente ligeras y eficientes, pero también efímeras. El motor de SwiftUI destruye y recrea estas vistas docenas de veces por segundo a medida que cambia la interfaz.

Cuando necesitas almacenar lógica de negocio compleja, conectarte a una base de datos o realizar llamadas a una API, no puedes poner ese código directamente en una vista efímera. Necesitas una clase (class, un tipo de referencia) que sobreviva a estos redibujados. Aquí es donde entra ObservableObject.

import SwiftUI
import Combine

class UserProfileViewModel: ObservableObject {
    @Published var username: String = "Invitado"
    @Published var followerCount: Int = 0
    
    func fetchUserData() {
        // Simulamos una llamada de red en Swift
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.username = "iOS_Ninja"
            self.followerCount = 1500
        }
    }
}

Al conformar nuestra clase a ObservableObject y usar el envoltorio @Published en sus propiedades, le estamos diciendo a SwiftUI: “Oye, cuando el valor de username o followerCount cambie, avisa a cualquier vista que esté prestando atención para que se redibuje”.

Pero, ¿cómo le dice la vista a SwiftUI que quiere “prestar atención” a esta clase? Ahí es donde entran nuestros dos protagonistas.


2. ¿Qué es @StateObject en SwiftUI?

Introducido por Apple en iOS 14, macOS 11 y watchOS 7, @StateObject fue la respuesta a un problema crítico de diseño en las primeras versiones de SwiftUI.

@StateObject se utiliza para crear y poseer una instancia de un ObservableObject.

Cuando marcas una propiedad con @StateObject, le estás garantizando a SwiftUI que esa vista es la “dueña absoluta” (owner) de ese objeto. SwiftUI tomará el control de la memoria de esa instancia y se asegurará de mantenerla viva y segura, sin importar cuántas veces se destruya y se vuelva a crear el struct de la vista.

¿Cuándo usar @StateObject?

La regla de oro para un iOS Developer es simple: Usa @StateObject la primera vez que instancias el objeto. Si tu vista contiene el signo igual (=) para crear la clase, debe ser un @StateObject.

struct UserProfileView: View {
    // La vista CREA y POSEE el ViewModel
    @StateObject private var viewModel = UserProfileViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Perfil de \(viewModel.username)")
                .font(.largeTitle)
            
            Text("Seguidores: \(viewModel.followerCount)")
                .font(.headline)
            
            Button("Cargar Datos") {
                viewModel.fetchUserData()
            }
        }
        .padding()
    }
}

Características clave de @StateObject:

  • Propiedad y Ciclo de vida: El ciclo de vida del objeto está atado al ciclo de vida de la vista en la pantalla, no a la estructura en sí. Si la vista desaparece de la jerarquía de navegación permanentemente, el objeto se libera de la memoria.
  • Inicialización perezosa (Lazy): SwiftUI solo inicializa el objeto la primera vez que se renderiza la vista.
  • Seguridad multiplataforma: Funciona de manera idéntica ya sea que estés compilando para la pantalla de un Apple Watch Series 9 en watchOS, un iPhone 15 en iOS, o un MacBook Pro en macOS utilizando el mismo proyecto de Xcode.

3. ¿Qué es @ObservedObject en SwiftUI?

@ObservedObject existe desde el nacimiento de SwiftUI (iOS 13). Al igual que su hermano menor, le dice a la vista que observe los cambios de un ObservableObject y se redibuje cuando los valores @Published cambien.

Sin embargo, hay una diferencia fundamental: @ObservedObject NO posee el objeto. Solo lo observa.

Cuando usas @ObservedObject, estás asumiendo que el objeto ya fue creado en otro lugar (generalmente por una vista padre usando @StateObject) y simplemente ha sido inyectado en esta vista.

¿Cuándo usar @ObservedObject?

Debes usarlo cuando pasas un objeto existente de una vista a otra. Es el mecanismo perfecto para la inyección de dependencias en la programación Swift.

// Vista Padre
struct MainAppView: View {
    // 1. El Padre CREA el objeto
    @StateObject private var viewModel = UserProfileViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Bienvenido de nuevo")
                // 2. El Padre PASA el objeto al hijo
                ProfileDetailsView(viewModel: viewModel)
            }
        }
    }
}

// Vista Hijo
struct ProfileDetailsView: View {
    // 3. El Hijo OBSERVA el objeto que recibió
    @ObservedObject var viewModel: UserProfileViewModel
    
    var body: some View {
        VStack {
            Text("Usuario: \(viewModel.username)")
            Button("Actualizar") {
                viewModel.fetchUserData()
            }
        }
    }
}

4. Semejanzas entre @ObservedObject y @StateObject en SwiftUI

Para un iOS Developer que recién comienza en SwiftUI, estos dos envoltorios de propiedades pueden parecer idénticos porque, en la superficie, hacen exactamente lo mismo:

  • Requieren ObservableObject: Ambos solo pueden usarse con clases que conformen el protocolo ObservableObject. Si intentas usarlos con un struct, Xcode te arrojará un error de compilación inmediato.
  • Suscripción a @Published: Ambos escuchan los cambios en las propiedades marcadas con @Published dentro de la clase.
  • Desencadenan redibujados (Renders): Cuando un valor cambia, tanto @StateObject como @ObservedObject invalidan el cuerpo (body) de la vista actual y obligan a SwiftUI a recalcular la interfaz para reflejar los nuevos datos.
  • Soporte de Bindings: Ambos permiten extraer “Bindings” (conexiones bidireccionales) utilizando el prefijo $. Por ejemplo, puedes pasar $viewModel.username a un TextField para que el usuario pueda editar su nombre y el modelo se actualice automáticamente.

5. Diferencias Críticas: El Peligro Oculto del Ciclo de Vida

Aquí es donde la teoría se encuentra con la práctica en la programación Swift, y donde muchos desarrolladores pasan horas depurando un comportamiento extraño en Xcode. La diferencia radica puramente en la retención de la memoria.

El Bug Clásico de SwiftUI

Imagina que ignoras la regla de oro y decides crear una instancia usando @ObservedObject en lugar de @StateObject.

struct BuggyView: View {
    // ❌ ERROR ARQUITECTÓNICO GRAVE
    @ObservedObject var viewModel = UserProfileViewModel()
    
    @State private var counter = 0
    
    var body: some View {
        VStack {
            Text("Usuario: \(viewModel.username)")
            
            Button("Cargar Usuario") {
                viewModel.fetchUserData()
            }
            
            Divider()
            
            Text("Contador de clics: \(counter)")
            Button("Incrementar contador local") {
                // Al hacer clic aquí, la vista se redibuja
                counter += 1 
            }
        }
    }
}

¿Qué ocurre aquí?

  1. Inicias la app. La vista aparece.
  2. Tocas “Cargar Usuario”. Después de 2 segundos, el nombre cambia de “Invitado” a “iOS_Ninja”. Todo parece funcionar.
  3. Luego, tocas “Incrementar contador local”.
  4. Al cambiar el estado local (counter), SwiftUI decide que necesita redibujar la vista.
  5. Destruye el struct BuggyView antiguo y crea uno nuevo.
  6. Al crear el nuevo struct, ejecuta de nuevo la línea @ObservedObject var viewModel = UserProfileViewModel().
  7. Como @ObservedObject no retiene el objeto, descarta el antiguo (con el nombre “iOS_Ninja”) y crea uno completamente nuevo en blanco.
  8. ¡Tu interfaz vuelve a mostrar “Invitado”! Has perdido todos los datos y el usuario se queda frustrado.

La Solución

Simplemente cambiando esa única línea a @StateObject, SwiftUI almacenará ese modelo de forma segura fuera de la vista. Cuando el contador incremente y la vista se redibuje, SwiftUI dirá: “Ya tengo este objeto guardado en la memoria, no lo voy a volver a instanciar”, y tus datos persistirán de manera impecable.

Tabla Comparativa Resumen

Característica@StateObject@ObservedObject
Rol PrincipalCreación y Propiedad del objeto.Observación e inyección de dependencia.
¿Quién retiene la memoria?SwiftUI (El framework lo mantiene vivo).La vista u objeto que lo inyectó desde arriba.
Cuándo usarloAl instanciar (= Model()).Al recibir como parámetro (var model: Model).
Impacto al redibujar la vistaEl objeto SOBREVIVE.El objeto SE DESTRUYE (si fue instanciado ahí mismo).
IntroduccióniOS 14, macOS 11, watchOS 7iOS 13, macOS 10.15, watchOS 6

6. Integración Multiplataforma en Xcode

Una de las grandes promesas de SwiftUI para un iOS Developer es la capacidad de usar la misma programación Swift en diferentes plataformas de Apple. El manejo de @ObservedObject y @StateObject en SwiftUI brilla en este aspecto.

Supongamos que estás construyendo una app para rastrear la ingesta de agua.

  • En iOS: Tendrás una pantalla rica con gráficos. Usarás @StateObject en la vista de inicio y pasarás ese mismo modelo a vistas secundarias (tarjetas, botones de añadir) usando @ObservedObject o el entorno (Environment).
  • En watchOS: El espacio en pantalla es mínimo. Tu ContentView principal del Apple Watch puede reutilizar exactamente el mismo archivo WaterTrackerViewModel.swift. Simplemente lo instancias de nuevo con @StateObject en la vista del reloj y le agregas una interfaz de usuario adaptada a la muñeca.
  • En macOS: En tu proyecto de Xcode, puedes tener una ventana flotante con una lista de tareas de hidratación, utilizando los mismos envoltorios sin cambiar una sola línea lógica de código.

Esta es la magia de separar el estado (View Models) de la vista. La lógica de negocio vive independientemente, garantizando escalabilidad y un código fácil de mantener y probar.


7. El Futuro: iOS 17 y la Macro @Observable

Como desarrollador siempre hay que mirar hacia adelante. A partir de iOS 17, macOS 14 y watchOS 10, Apple introdujo en la programación Swift un nuevo paradigma a través de las “Macros”.

Con la macro @Observable, la necesidad de usar @Published, @StateObject y @ObservedObject se reduce enormemente. Con el nuevo sistema, simplemente anotas tu clase con @Observable y usas @State directamente en tu vista, independientemente de si es un tipo de valor o de referencia.

Sin embargo, como iOS Developer, lidiarás con bases de código que deben soportar iOS 15 o 16 durante muchos años. El dominio de @ObservedObject y @StateObject en SwiftUI sigue siendo una habilidad indispensable para cualquier entrevista técnica y para el mantenimiento de proyectos existentes en Xcode.


Conclusión

El dominio del estado de los datos es lo que separa a un desarrollador novato de un arquitecto de software sólido. Al comprender la diferencia en el ciclo de retención de memoria entre @ObservedObject y @StateObject en SwiftUI, puedes evitar los errores más comunes y frustrantes en el renderizado de interfaces.

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

Mejor IA Coding Agent para Xcode

Next Article

Listas editables en SwiftUI

Related Posts