@State ahora es un macro en Xcode 27: qué cambia realmente en SwiftUI
Arturo Rivas Arias
Durante la WWDC 2026 Apple presentó numerosos cambios en SwiftUI, algunos muy visibles y otros más discretos. Entre estos últimos se encuentra una modificación especialmente interesante para quienes desarrollamos interfaces declarativas a diario: @State deja de estar implementado como un property wrapper tradicional y pasa a convertirse en una macro del lenguaje Swift.
A primera vista parece un detalle interno sin demasiada relevancia. Seguimos escribiendo exactamente el mismo código que escribíamos ayer. Sin embargo, este cambio resuelve algunas ineficiencias históricas de SwiftUI y mejora especialmente la integración con el nuevo sistema de Observation.
La sintaxis no cambia
Para la mayoría de desarrolladores, el uso cotidiano de @State seguirá siendo exactamente el mismo.
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") {
count += 1
}
}
}
Las recomendaciones de Apple continúan siendo idénticas:
- Declarar el estado como
private. - Inicializarlo donde se define.
- Utilizarlo para datos cuyo ciclo de vida pertenece a la propia vista.
- Permitir que SwiftUI gestione su almacenamiento.
La diferencia se encuentra en la implementación interna.
Cómo funciona realmente @State
Uno de los conceptos más importantes para entender SwiftUI es recordar que una vista no es una instancia persistente. Las vistas son tipos por valor que SwiftUI recrea continuamente durante el proceso de renderizado. Sin embargo, los datos almacenados mediante @State sobreviven a esas recreaciones.
Cuando escribimos algo como:
@State private var username = ""
SwiftUI no guarda ese valor directamente dentro de la estructura de la vista. En realidad, crea un almacenamiento persistente asociado a la identidad de dicha vista y conecta la propiedad con ese almacenamiento. Gracias a ello, el valor sigue existiendo aunque la estructura sea recreada docenas o cientos de veces. Hasta Xcode 26 toda esta infraestructura se generaba mediante un property wrapper. Con Xcode 27 pasa a generarse mediante macros.
El problema histórico con los objetos observables
La mejora más importante aparece cuando almacenamos objetos observables dentro de @State. Consideremos este ejemplo:
@Observable
final class UserStore {
init() {
print("Inicializando UserStore")
}
}
struct ProfileView: View {
@State private var store = UserStore()
var body: some View {
Text("Perfil")
}
}
Muchos desarrolladores asumían que UserStore() se ejecutaba una única vez. La realidad era algo distinta.
Cada vez que SwiftUI reconstruía la vista, la expresión de inicialización volvía a ejecutarse. SwiftUI descartaba posteriormente esas instancias adicionales y conservaba únicamente la original, pero el trabajo realizado por el inicializador ya se había producido. En inicializadores simples esto apenas tenía impacto.
Sin embargo, en escenarios reales podía significar:
- Apertura de conexiones de red.
- Lectura intensivas de disco.
- Creación de observadores dentro de la instancia.
- Construcción de cachés para almacenamiento temporal.
- Operaciones costosas de configuración.
Todo ello para instancias que terminarían siendo descartadas inmediatamente.
La inicialización ahora es diferida
Con la nueva implementación basada en macros, SwiftUI adopta una estrategia mucho más eficiente. La instancia se crea únicamente cuando SwiftUI necesita construir el almacenamiento real asociado al estado. Esto significa que:
@State private var store = UserStore()
ya no ejecutará repetidamente el inicializador cuando la vista sea recreada.
El comportamiento coincide ahora con lo que la mayoría de desarrolladores esperaba intuitivamente desde el principio. Además de reducir trabajo innecesario, también disminuye la posibilidad de provocar efectos secundarios inesperados durante el ciclo de renderizado.
Un ejemplo práctico
Imaginemos una aplicación que gestiona descargas.
@Observable
final class DownloadManager {
var activeDownloads = 0
init() {
print("Configurando gestor")
}
}
La vista propietaria puede almacenar el objeto directamente en @State.
struct DownloadsView: View {
@State private var manager = DownloadManager()
var body: some View {
VStack {
Text("Descargas activas: \(manager.activeDownloads)")
}
}
}
En Xcode 27 el gestor se inicializará únicamente cuando el almacenamiento del estado sea realmente creado, evitando ejecuciones repetidas del inicializador.
Ya no necesitamos ciertos workarounds
Debido al comportamiento anterior, algunos desarrolladores recurrían a patrones similares a este:
struct ContentView: View {
@State private var manager: DownloadManager?
var body: some View {
Group {
if let manager {
DownloadsView(manager: manager)
}
}
.task {
if manager == nil {
manager = DownloadManager()
}
}
}
}
El objetivo era evitar múltiples inicializaciones. Con Xcode 27 este tipo de solución deja de ser necesaria cuando el único problema era impedir la ejecución repetida del inicializador. El código puede volver a simplificarse utilizando directamente @State.
Lo que sigue siendo una mala idea
Pero hay algo que no ha cambiado. Inicializar el estado desde el init de una vista continúa siendo problemático.
struct ProfileView: View {
@State private var store: UserStore
init() {
store = UserStore()
}
var body: some View {
Text("Perfil")
}
}
El inicializador de una vista puede ejecutarse muchas veces durante la vida de la aplicación. Por tanto, cada nueva construcción volverá a crear una instancia adicional de UserStore, aunque SwiftUI termine ignorándola para conservar el estado original. Es decir: los efectos secundarios seguirán ocurriendo. La recomendación sigue siendo inicializar el estado directamente en la declaración de la propiedad.
Atención cuando dependemos de valores externos
Otro escenario delicado aparece cuando el estado se construye utilizando información recibida desde una vista padre.
struct UserDetailView: View {
@State private var store: UserStore
init(userID: String) {
store = UserStore(id: userID)
}
var body: some View {
Text(store.name)
}
}
A primera vista parece razonable. Sin embargo, si userID cambia posteriormente, SwiftUI no reemplazará automáticamente el valor almacenado en @State. El estado seguirá conservando la instancia original. Esto puede provocar inconsistencias entre los datos recibidos desde el parámetro y el estado interno de la vista.
Cuando existe dependencia de valores externos suelen ser más apropiadas otras estrategias como:
- Estado derivado, por ejemplo con variables calculadas.
- Usar
task(id:). - Inyección como dependecia.
- Cambios explícitos de identidad mediante
id(...).
@State sigue siendo el propietario ideal de un @Observable
Desde la llegada del framework Observation, Apple recomienda utilizar @State para almacenar objetos observables cuyo ciclo de vida pertenece a la propia vista.
Por ejemplo:
@Observable
final class SessionManager {
var isLoggedIn = false
}
struct LoginView: View {
@State private var session = SessionManager()
var body: some View {
SessionStatusView(session: session)
}
}
Las vistas hijas pueden recibir la referencia directamente.
struct SessionStatusView: View {
let session: SessionManager
var body: some View {
Text(session.isLoggedIn ? "Conectado" : "Desconectado")
}
}
No es necesario utilizar Binding simplemente para acceder o modificar propiedades internas de un objeto observable.
Qué nos indica este cambio sobre SwiftUI
La transformación de @State en una macro es otro ejemplo de cómo Apple está integrando progresivamente las capacidades metaprogramáticas de Swift dentro de sus frameworks. Durante los últimos años hemos visto llegar:
@Observable@Model#Preview- Macros personalizadas
Ahora uno de los mecanismos más fundamentales de SwiftUI también adopta esta tecnología. Aunque la sintaxis permanezca estable, Apple obtiene mayor flexibilidad para evolucionar la implementación interna del framework sin afectar al código de las aplicaciones.
Conclusión
La conversión de @State en una macro puede parecer un simple detalle técnico, pero aporta una mejora práctica muy relevante.
Los objetos @Observable almacenados mediante @State dejan de inicializarse repetidamente cuando SwiftUI recrea una vista. Esto reduce trabajo innecesario, evita efectos secundarios inesperados y acerca el comportamiento real al modelo mental que la mayoría de desarrolladores ya tenía sobre el estado en SwiftUI.
Lo más interesante es que todo esto llega sin cambiar una sola línea de código. La API permanece intacta mientras SwiftUI se vuelve más eficiente por dentro, una combinación que suele ser la mejor señal de una buena evolución del framework.