¿Por qué SwiftUI prefiere structs sobre classes, y cuándo sigues necesitando una class?
Las vistas de SwiftUI son value types baratas y desechables por diseño — entender por qué desbloquea cómo funcionan realmente el diffing, el estado y la identidad.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Por qué SwiftUI prefiere structs sobre classes, y cuándo sigues necesitando una class?
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.#Structs por defecto
El protocolo central View de SwiftUI se implementa casi siempre con structs, no con classes. Cada Text, HStack y vista personalizada que escribes en el día a día es un value type. Esto no es un accidente de estilo: es fundamental para cómo el framework decide cuándo y qué redibujar. Cuando un entrevistador hace esta pregunta, en realidad está evaluando si entiendes el cambio de modelo mental que exige SwiftUI frente a los view controllers y vistas basados en referencias de UIKit.
#La semántica de valor elimina toda una clase de bugs
Un struct se copia al asignarlo o al pasarlo entre funciones; dos variables que "contienen lo mismo" son independientes después de la copia (Swift optimiza esto con copy-on-write en el almacenamiento interno, así que la copia es barata hasta que un lado muta). Eso significa que un struct View que SwiftUI captura para calcular un frame no puede ser mutado por debajo desde otra parte de la app que tenga una referencia — no hay una referencia compartida y mutable que proteger. Las classes, en cambio, reparten referencias compartidas: si dos objetos tienen una referencia a la misma instancia, una mutación desde uno es visible para el otro, lo cual es una fuente común de bugs de estado obsoleto cuando un view controller y una tarea en background sostienen el mismo objeto.
#Los structs son la razón por la que el ciclo de renderizado es barato
SwiftUI no muta una jerarquía de vistas persistente como UIKit muta la propiedad view de un view controller. En cambio, cada vez que cambia el estado, SwiftUI vuelve a llamar a body y construye un árbol completamente nuevo de structs View que describe cómo debería verse la interfaz, y luego compara ese árbol con el anterior para averiguar qué necesita cambiar realmente en pantalla. Los structs hacen esto viable: crear y descartar miles de value types pequeños en cada frame es prácticamente gratis — sin asignación en el heap, sin tráfico de retain/release, sin referencias colgantes que limpiar. Si View fuera una jerarquía de classes, cada re-renderizado asignaría y eventualmente liberaría un árbol de objetos, y las llamadas de retain/release de ARC se convertirían en un costo real multiplicado por cada cambio de estado que dispara un píxel.
#La identidad es un asunto separado del tipo
Algunos entrevistadores insisten: "¿pero SwiftUI no necesita saber cuál vista es cuál entre renderizados?" Sí lo necesita — pero eso se resuelve mediante identidad estructural (la posición y el tipo de una vista dentro del árbol) o identidad explícita con .id(_:), no mediante la identidad de referencia del propio struct. Una instancia de class tiene identidad gratis — === compara si dos referencias apuntan al mismo objeto en memoria —, pero SwiftUI deliberadamente no se apoya en eso, porque el punto entero es que el struct que describe el estado de una pantalla hoy es un valor desechable, no un objeto persistente contra el cual comparar punteros.
#Dónde una class sigue siendo la herramienta correcta
Nada de esto significa que las classes estén mal en código SwiftUI — significa que recurres a una cuando de verdad necesitas semántica de referencia: estado mutable compartido que varias vistas deben observar y mutar in situ (un view model que respalda varias pantallas), objetos con identidad y ciclo de vida real, independientes del re-renderizado de cualquier vista (un gestor de sesión de red, un reproductor de audio, un central de Core Bluetooth), o cualquier cosa que deba interoperar con APIs de Objective-C/UIKit que exigen heredar de NSObject (delegados, o un UIViewControllerRepresentable que envuelve un UIViewController real). Desde iOS 17, el macro @Observable permite que una class normal se sume a la observación granular de SwiftUI sin el boilerplate de ObservableObject + @Published, pero sigue siendo una class por debajo, elegida específicamente porque el estado necesita compartirse por referencia.
#En código
// Una View de SwiftUI es un struct — barata de crear y descartar en cada render.struct ProfileBadge: View {let name: Stringlet isOnline: Boolvar body: some View {HStack {Circle().fill(isOnline ? .green : .gray).frame(width: 8, height: 8)Text(name)}}}// Una class sigue siendo la herramienta correcta cuando el estado debe compartirse por referencia.@Observablefinal class SessionStore {var currentUser: User?var isAuthenticated: Bool { currentUser != nil }func signIn(as user: User) {currentUser = user}}struct RootView: View {@State private var session = SessionStore()var body: some View {if let user = session.currentUser {ProfileBadge(name: user.name, isOnline: true)} else {LoginView(session: session)}}}