¿Cómo se reevalúa el body de una View de SwiftUI, y qué implica eso para el diseño del estado?
SwiftUI no actualiza una pantalla — vuelve a hacer la misma pregunta, compara la respuesta y escribe solo la diferencia; entender ese ciclo es la diferencia entre pelear contra el framework y diseñar estado que fluye con él.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo se reevalúa el body de una View de SwiftUI, y qué implica eso para el diseño del estado?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.La respuesta honesta, un poco incómoda, es la primera que conviene dar: el body de una vista de SwiftUI no es un método que vos llamás, es una propiedad computada pura que el runtime de SwiftUI llama, tantas veces como decida, cada vez que cree que cambió algún estado relevante para esa vista. Vos no controlás cuándo corre. Solo controlás qué devuelve, y qué tan caro es calcular eso que devuelve. Los entrevistadores hacen esta pregunta no para escuchar la tabla de property wrappers recitada de memoria — quieren saber si podés explicar por qué una pantalla "parpadea" cuando el timestamp de una sola fila tickea, o por qué una animación reinicia sin motivo aparente. Las dos cosas son consecuencia directa del mecanismo de abajo.
Que haya un cambio de estado "relevante" significa uno de un conjunto chico de disparadores: que mute un valor dueño de un @State, que se escriba un @Binding (que reenvía a la propiedad @State o @Published que es la verdadera fuente de verdad), que el objeto envuelto por un @ObservedObject o @StateObject publique a través de objectWillChange (que dispara automáticamente cada propiedad @Published que tiene), que cambie un @EnvironmentObject o un valor de @Environment, o — desde que llegó el framework de Observation en iOS 17 — que cambie una propiedad específica de un tipo con el macro @Observable que ese body efectivamente leyó la última vez que corrió.
Una vez que una vista queda marcada como inválida, esto es lo que pasa realmente, de punta a punta:
Hay dos etapas acá, y confundirlas es el error más común. La primera — llamar a body y recibir un árbol de valores View— es barata por construcción: todavía no se tocó nada en pantalla, solo se produjo una descripción de tipo valor, mayormente structs, de cómo debería verse la pantalla. La segunda etapa — el diff del reconciler contra el árbol anterior, y recién ahí escribir el puñado de atributos que efectivamente cambiaron hacia el árbol de render real — es donde se filtra el trabajo caro. Este diseño en dos etapas es justamente por qué SwiftUI se puede dar el lujo de llamar a body con mucha más libertad de la que una vista de UIKit llamaría a layoutSubviews: el checkpoint es el diff, no la llamada.
El diff en sí corre sobre identidad estructural: por defecto, la identidad de un nodo es su tipo más su posición entre los hijos de su padre. Dos renders consecutivos que ponen un Text en el mismo lugar se tratan como "el mismo" Text, aunque se haya asignado un struct Text completamente nuevo para describirlo. Podés anular ese default con un .id(_:) explícito. Equivocarse en cualquiera de las dos direcciones produce un bug muy específico: darle a dos ramas de un if/else el mismo id explícito cuando son vistas semánticamente distintas hace que SwiftUI arrastre estado entre ellas que no debería haber sobrevivido; al revés, reordenar una lista sin darle a sus filas una identidad estable atada a los datos hace que SwiftUI trate cada fila como recién creada en cada reorden — el @State vuelve a su valor inicial, .onAppear se vuelve a disparar, una animación en curso arranca de cero.
Acá es donde el diseño del estado deja de ser trivia y empieza a tener consecuencias reales. El objectWillChange de un ObservableObject no te puede decir cuál @Published cambió efectivamente — se dispara por cualquiera de ellas, lo que significa que una vista que solo lee usuario.nombre de un objeto compartido igual se vuelve a renderizar cada vez que un usuario.ultimaConexion sin relación tickea una vez por segundo. El macro @Observable arregla exactamente eso instrumentando el acceso a las propiedades: una vista queda inválida solo por las propiedades específicas que efectivamente leyó la última vez que corrió su body, así que ese mismo tick por segundo sobre un campo que la vista nunca toca no le genera ningún trabajo. Migrar un ObservableObject grande y muy compartido a @Observable es una de las correcciones de rendimiento de mayor impacto y menor riesgo disponibles en un código base de SwiftUI maduro, porque no cambia nada en el lugar donde se lo usa — solo cambia la granularidad de la invalidación.
Las reglas prácticas que se desprenden de todo esto son cortas, y se pueden decir en voz alta en una entrevista: mantené el @State tan local como el problema que representa, porque todo lo que promovés a un objeto compartido amplía a quién se invalida; asumí que cualquier cambio de un campo @Published en un ObservableObject clásico invalida a todos sus observadores, así que dividí los objetos grandes por responsabilidad o migrá a @Observable; y mantené el body en sí liviano — nada de asignaciones pesadas, decodificación sincrónica, ni formateo de fechas adentro de un loop — porque de verdad puede correr más seguido de lo que el modelo mental ingenuo espera, y ninguna maquinaria de diffing de más abajo te salva de hacer trabajo caro antes de que le llegue el turno.
🧠 Check your understanding
0/3 · 0/3 answered1. Una vista observa un ObservableObject con dos propiedades @Published, titulo y progreso. El body de la vista solo lee titulo. ¿Qué pasa cuando cambia progreso?
2. ¿Qué usa SwiftUI principalmente para decidir si una vista en la salida de una llamada a body nueva es la misma vista que la de la llamada anterior?
3. ¿Por qué se considera barato que SwiftUI llame al body de una vista mucho más seguido de lo que una vista de UIKit llamaría a layoutSubviews?