var name = "". Es la línea más usada de Swift, pero hay más opciones de las que parece para este lugar: propiedades stored y computed, lazy, willSet y didSet, y propiedades de tipo. Aunque ya conozcas la sintaxis, el criterio suele difuminarse al preguntar: «¿este valor debe ser lazy o computed?».
Este es el tercer artículo de la serie básica de Swift. Resume los cinco tipos de propiedades según los criterios de elección, no como una lista de sintaxis. La pregunta clave es una: ¿este valor se almacena, se calcula y cuándo se crea?
Antes de elegir una propiedad de tipo solo porque necesitas estado compartido, revisa también El coste de probar singletons en Swift; así quedarán más claros los límites entre static let y static var.
Stored frente a computed: valores en memoria y valores calculados al acceder
La primera bifurcación al elegir una propiedad es decidir si almacena o calcula.
Una stored property ocupa espacio real dentro de la instancia. Si escribes var name = "Kim", se reserva espacio para name en la memoria de la instancia. El tamaño de un struct se determina por la suma de sus stored properties.
Una computed property no ocupa espacio. Ejecuta código cada vez que se accede a ella para producir un valor; en la práctica, es una función.
struct Rectangle {
var width: Double // stored
var height: Double // stored
var area: Double { // computed — sin espacio de almacenamiento
width * height
}
}
También podrías haber hecho area una stored property. Pero entonces tendrías que actualizarla cada vez que cambiara width, y cualquier desajuste sería un bug. Aquí aparece el primer criterio: los valores derivados de otros deben calcularse, no almacenarse. Mantener una única fuente de verdad elimina de raíz los bugs de sincronización.
Una computed property también puede escribirse añadiendo set. Puedes almacenar celsius y calcular fahrenheit; al asignar fahrenheit, recalculas y actualizas celsius. Incluso aquí solo hay una verdad almacenada; lo demás son ventanas hacia ella.
También conviene saber cuándo usar una computed property en lugar de una función. La convención de las guías de diseño de API de Apple es esta: si el cálculo es barato (normalmente cercano a O(1)), no tiene efectos secundarios y conceptualmente es «una propiedad del objeto», usa una propiedad. Si es costoso o tiene efectos secundarios, usa un método. Por eso array.count es una propiedad, mientras que array.sorted() es un método.
lazy: una stored property creada en el primer acceso
lazy es la tercera opción. Es una stored property que se inicializa al acceder por primera vez, no al crear la instancia.
class ImageProcessor {
lazy var filters: [Filter] = loadExpensiveFilters()
}
Hay dos casos principales. Primero, valores costosos de inicializar que quizá nunca uses: preparar recursos pesados en cada instancia sería desperdiciarlos. Segundo, propiedades que necesitan referenciar self. El valor predeterminado de una stored property normal se calcula antes de que termine init, así que no puede usar self; con lazy, la inicialización se retrasa hasta el primer acceso y la referencia se permite. Por eso el closure autoejecutable (= { ... }()), visto al hablar de closures, suele aparecer junto a lazy.
Hay dos advertencias. lazy debe ser var: el valor cambia de un estado sin inicializar, parecido a nil, a su valor real, así que no puede ser let. Además, no es thread-safe. Si varios hilos acceden por primera vez al mismo tiempo, la inicialización podría ejecutarse dos veces. Si un valor debe crearse una sola vez en un entorno multihilo, necesitas otro mecanismo.
La diferencia con una computed property es clara. lazy calcula una vez, guarda el resultado y después lo devuelve. Una computed property recalcula cada vez. «Costoso pero inmutable» significa lazy; «barato pero siempre actualizado», computed.
willSet y didSet: reaccionar a los cambios de valor
Las stored properties admiten observadores de cambios. Son código que se ejecuta justo antes (willSet) y justo después (didSet) de cambiar el valor.
class ProgressBar {
var progress: Double = 0 {
didSet {
// oldValue se proporciona automáticamente
guard progress != oldValue else { return }
updateUI()
}
}
}
Su uso típico es automatizar lo que debe ocurrir cuando cambia un valor: actualizar la UI, registrar eventos o validar valores (y revertirlos si salen del rango). Puedes añadir efectos secundarios sin crear un setter manual ni abandonar la sintaxis de asignación.
Hay dos reglas prácticas que suelen confundir. Primero, asignar un valor dentro de init no llama a los observadores. La inicialización es «configuración», no «cambio»; por eso la actualización de UI de didSet no se aplica al valor inicial. Segundo, cambiar una propiedad de un valor-type también llama al didSet de la propiedad que lo contiene. Como en person.name = "Lee", modificar el interior de un struct llama al didSet de person. La semántica vista al hablar de value types —«modificar equivale a reemplazarlo por un valor nuevo»— también se cumple aquí.
Conviene evitar abusar de didSet. Si acumulas lógica allí, resulta difícil seguir qué hace una simple asignación. Cambiar un valor y provocar una solicitud de red viola el principio de mínima sorpresa. Mantén en didSet solo sincronizaciones ligeras y mueve la lógica pesada a métodos explícitos.
Propiedades de tipo: valores asociados al tipo, no a la instancia
Al añadir static, la propiedad pertenece al tipo, no a la instancia.
struct APIConfig {
static let baseURL = URL(string: "https://api.example.com")!
static var requestCount = 0
}
Crees las instancias que crees, solo hay una propiedad de tipo. Encaja bien para colecciones de constantes (configuración o formateadores compartidos), y la biblioteca estándar también las usa mucho en lugares como Int.max y Double.pi.
Un dato útil: static let garantiza una inicialización lazy thread-safe. Se inicializa una sola vez en el primer acceso y es segura ante accesos simultáneos. Esa garantía existe en static, pero no en lazy var. También fundamenta que static let shared de un singleton funcione sin bloqueos adicionales. En cambio, un static var mutable equivale prácticamente a una variable global: dificulta aislar pruebas y puede provocar carreras de datos. Ya expliqué en otro artículo por qué los singletons se consideran un antipatrón; aquí basta con recordar que static var debe ser el último recurso.
Diagrama de decisión: resumen en cuatro preguntas
Los cinco tipos pueden resumirse con una pregunta para cada caso:
- ¿Se deriva de otro valor? → computed property. Almacena una sola verdad.
- ¿La inicialización es costosa o necesita self? → lazy var. Cuidado con el primer acceso multihilo.
- ¿Hay una acción que deba seguir al cambio de valor? → stored property + didSet. Solo para acciones ligeras.
- ¿Debe existir una sola, independiente de las instancias? → static. Si es let, además ofrece inicialización lazy segura.
- Si no se cumple nada de lo anterior → una stored property normal. En la mayoría de los casos, esa es la respuesta.
Los property wrappers como @State y @Published de SwiftUI también se construyen sobre este sistema de propiedades. Si entiendes stored, computed y los observadores, se ve que un wrapper «envuelve una stored property y automatiza comportamientos del tipo de didSet». La creación de property wrappers propios se tratará en la serie intermedia.
Resultados verificados mediante ejecución directa
En Apple Swift 6.3.3 registré en un mismo objeto el número de inicializaciones de lazy, el valor anterior y el nuevo de didSet, y el resultado de una computed property. La salida muestra dos lecturas de la propiedad lazy y el cambio de una stored property de 0 a 7.
properties=lazy-builds:1,didSet:0->7,computed:14
Por este pequeño resultado, si una caché debe crearse exactamente una vez, no confío solo en la sintaxis lazy. Si puede haber accesos simultáneos, pruebo el número de inicializaciones y compruebo si hace falta sincronización adicional. En cambio, en didSet dejo únicamente acciones ligeras que observen los cambios de estado, como en la salida anterior, y traslado el I/O susceptible de fallar a métodos explícitos.
Resumen
- El primer criterio para una propiedad es si almacena o calcula. Los valores derivados de otros deben calcularse para mantener una única fuente de verdad.
- lazy es una stored property que se inicializa en el primer acceso y resuelve la inicialización costosa y la referencia a self. El precio es que exige var y no es thread-safe.
- willSet/didSet permiten reaccionar a cambios, pero no se llaman durante init y dificultan el seguimiento si contienen lógica pesada.
- static let es una propiedad de tipo con inicialización lazy thread-safe garantizada; static var representa estado global y debe ser el último recurso.
El próximo artículo será la cuarta entrega básica: guard. Analizaremos a fondo la salida temprana que vimos brevemente al hablar de opcionales y explicaremos por qué la comunidad de Swift libra una guerra contra la indentación.
Lecturas recomendadas
- La verdadera naturaleza de los opcionales de Swift: en realidad son un enum (hasta cinco criterios prácticos para desenvolverlos)
- [Swift básico #4] Cómo usar Swift guard correctamente: salida temprana para derribar la pirámide de la perdición
- [Swift básico #5] Mapa completo del manejo de errores en Swift: cuándo usar throws, try?, try! y Result
Fuentes y verificación
- The Swift Programming Language: PropertiesSwift.org · Documentación oficial · Consultado 26 de agosto de 2026Respalda: Reglas del lenguaje para propiedades stored, computed, lazy, observadores y de tipo

![Imagen de portada de [Swift #3] Propiedades Swift: guía de elección](/assets/images/posts/7621582a-1bb6-4f5a-8fca-12fae48417af/1.jpg)