🔄 Muchos desarrolladores experimentados siguen teniendo dudas sobre cómo gestionar el ciclo de vida de las clases @Observable en SwiftUI. El problema no es solo técnico: es conceptual. Cuando creamos una clase @Observable en una vista SwiftUI, podría parecer que no necesitamos @State porque las propiedades se actualizan automáticamente. Pero aquí está el peligro oculto.
⚠️ Sin @State, tu modelo no está vinculado al ciclo de vida de la vista. SwiftUI recreará una nueva instancia cada vez que la estructura de la vista se reinicialice. Y eso pasa más de lo que crees. Imagina este escenario: tienes un contador en una vista hija. Cambias un color en la vista padre. El body se reconstruye, el inicializador de la vista hija se ejecuta nuevamente, y boom. Tu contador vuelve a cero sin razón aparente.
@Observable
class Counter {
var value = 0
init() {
print("Counter inicializado")
}
}
struct ParentView: View {
@State private var color: Color = .blue
var body: some View {
VStack {
ChildView()
Button("Cambiar color") {
color = color == .blue ? .red : .blue
}
.foregroundStyle(color)
}
}
}
struct ChildView: View {
let counter = Counter() // Se reinicia en cada reconstrucción
var body: some View {
VStack {
Text("Contador: \(counter.value)")
Button("Incrementar") {
counter.value += 1
}
}
}
}
🎯 La solución es clara: marca tu clase @Observable con @State cuando la crees en una vista. Esto le dice a SwiftUI que gestione su almacenamiento y la mantenga viva mientras la vista existe. La vista puede recrearse mil veces, pero el estado persiste. Otro problema silencioso: aunque el estado persista correctamente con @State, el inicializador de tu clase se ejecuta cada vez que se reconstruye la vista y si tienes lógica pesada ahí dentro, afectará el rendimiento.
struct ChildView: View {
@State private var counter = Counter() // Persiste correctamente
var body: some View {
VStack {
Text("Contador: \(counter.value)")
Button("Incrementar") {
counter.value += 1
}
}
}
}
⏱️ Aquí es donde entra el patrón de inicialización diferida: haz tu propiedad @State opcional y créala dentro de un modificador .task(). Así el inicializador solo se ejecuta una vez, cuando la vista se añade a la jerarquía.
@Observable
class HeavyModel {
var data: [String] = []
init() {
print("Inicialización pesada ejecutada")
// Simulación de operación costosa
Thread.sleep(forTimeInterval: 1)
}
func loadData() {
data = ["Item 1", "Item 2", "Item 3"]
}
}
struct OptimizedView: View {
@State private var model: HeavyModel? // Opcional
var body: some View {
Group {
if let model {
List(model.data, id: \.self) { item in
Text(item)
}
} else {
ProgressView()
}
}
.task {
if model == nil { // Solo se ejecuta una vez
model = HeavyModel()
model?.loadData()
}
}
}
}
➕ Si necesitas configurar tu modelo basándote en un valor externo, usa .task(id:) para que se vuelva a ejecutar cuando ese valor cambie. Es el patrón ideal para datos que dependen de selecciones o estados dinámicos.
@Observable
class UserProfile {
var username: String
var posts: [String] = []
init(userId: String) {
self.username = "User \(userId)"
print("Cargando perfil para: \(userId)")
}
func loadPosts() {
posts = ["Post 1", "Post 2", "Post 3"]
}
}
struct ProfileView: View {
let userId: String
@State private var profile: UserProfile?
var body: some View {
Group {
if let profile {
VStack(alignment: .leading) {
Text(profile.username)
.font(.title)
ForEach(profile.posts, id: \.self) { post in
Text(post)
}
}
} else {
ProgressView()
}
}
.task(id: userId) { // Se ejecuta cuando userId cambia
profile = UserProfile(userId: userId)
profile?.loadPosts()
}
}
}
🌍 Para estado global compartido entre todas las escenas de tu app, inicializa tu @Observable en el struct App y pásalo por el entorno. Cada ventana accederá a la misma instancia. En dispositivos con múltiples escenas (iPad, Mac), el estado creado en WindowGroup es independiente por ventana. Si quieres compartir datos entre todas ellas, debes subirlo al nivel App. Pero cuidado con qué compartes.
📊 La diferencia entre @Observable y ObservableObject también importa: @Observable rastrea solo las propiedades que realmente lees en la vista. Mejor rendimiento, menos rerenderizados innecesarios.
👨💻 Dominar la inicialización correcta de clases @Observable marca la diferencia entre una app que funciona y una que escala de forma profesional. ¿Ya aplicas estos patrones en tus proyectos?


Deja un comentario