Structs vs clases: valor y referencia
La decisión de diseño más importante de Swift. Semántica de valor vs referencia, cuándo usar cada una, y por qué Swift prefiere structs.
Structs y clases se parecen —ambas agrupan datos y comportamiento— pero se comportan de forma radicalmente distinta al copiarlas. Entender esa diferencia es lo que separa a un principiante de alguien que escribe Swift idiomático.
- Definir structs y clases.
- La diferencia clave: valor vs referencia.
- Métodos
mutatinge inicializadores. - Cuándo usar cada una (y por qué Swift prefiere structs).
Lo que comparten
Ambas agrupan propiedades y métodos:
struct Punto {
var x: Double
var y: Double
func distanciaAlOrigen() -> Double {
(x * x + y * y).squareRoot()
}
}
let p = Punto(x: 3, y: 4) // init por miembros, automático en structs
print(p.distanciaAlOrigen()) // 5.0
Las structs traen un inicializador por miembros gratis. Las clases no: tienes que escribirlo.
La diferencia clave: valor vs referencia
Aquí está todo. Una struct es un tipo de valor: al asignarla o pasarla, se copia. Una clase es un tipo de referencia: se comparte la misma instancia.
struct PuntoV { var x: Int }
class PuntoR { var x: Int; init(x: Int) { self.x = x } }
var a = PuntoV(x: 1)
var b = a // COPIA
b.x = 99
print(a.x) // 1 ← 'a' no cambió
var c = PuntoR(x: 1)
var d = c // MISMA instancia
d.x = 99
print(c.x) // 99 ← 'c' sí cambió, es la misma
Con valor (structs), cada quien tiene su copia: nadie modifica tus datos a tus espaldas. Esto elimina una categoría entera de bugs — los efectos secundarios inesperados y las condiciones de carrera. Por eso SwiftUI usa structs para las vistas y el estado: predecibilidad. Con referencia (clases), compartes una instancia, lo cual es útil cuando de verdad quieres que algo sea único y compartido (un gestor, una conexión). La regla mental: valor = datos; referencia = identidad.
Métodos mutating
Como una struct es de valor, un método que cambie sus propiedades debe marcarse mutating:
struct Contador {
var valor = 0
mutating func incrementar() { valor += 1 }
}
var c = Contador()
c.incrementar() // solo funciona si 'c' es var
Las clases no lo necesitan (siempre pueden mutar sus propiedades).
Inicializadores
Las structs generan el init por miembros. Las clases lo requieren explícito:
class Usuario {
let nombre: String
var activo: Bool
init(nombre: String, activo: Bool = true) {
self.nombre = nombre
self.activo = activo
}
}
Identidad: ===
Como las clases se comparten, puedes preguntar si dos variables apuntan a la misma instancia con ===:
let x = Usuario(nombre: "Ana")
let y = x
print(x === y) // true: la misma instancia
Las structs no tienen identidad (cada copia es independiente), así que === no aplica.
¿Cuál uso?
Struct (por defecto)
Datos: un modelo, una configuración, coordenadas, una respuesta de API. Swift y Apple recomiendan structs salvo que necesites lo contrario.
Clase (cuando la necesitas)
Identidad compartida o herencia: un gestor único, un objeto que muchas partes deben ver cambiar, interoperar con APIs de Objective-C.
La guía oficial de Swift es clara: usa structs por defecto y pasa a clase solo cuando tengas una razón concreta (identidad compartida, herencia de clase, o requisitos de un framework). La mayoría de tus tipos deberían ser structs.
- Crea una
structcon una propiedad, cópiala a otra variable y cambia la copia. Comprueba que el original no cambia. - Haz lo mismo con una
classy observa que sí cambia. - Añade un método
mutatinga tu struct. - Escribe un
initpersonalizado para una clase con un parámetro por defecto. - Comprueba identidad con
===en dos variables de la misma instancia.