Al desarrollar para iOS, acabas usando lazy var con mucha frecuencia.
A mí también me pasaba. Lo añadía por costumbre para posponer la inicialización de objetos pesados.
Entonces me surgió una duda: ¿acaso lazy no es, en realidad, el mismo patrón Proxy de los libros de patrones de diseño?
Empecemos por la conclusión.
lazyes la versión integrada en el lenguaje de un Virtual Proxy que retrasa la creación hasta que se necesita el objeto real.
Hoy explicaré qué es el patrón Proxy en Swift y cómo se oculta este concepto tras la palabra clave lazy, siguiendo el orden en que lo entendí al trabajar directamente con el código.
Veamos primero el resumen clave.
- El patrón Proxy es un patrón estructural que controla el acceso colocando un intermediario delante del objeto real.
lazytiene el mismo objetivo que un Virtual Proxy que retrasa la creación.- Sin embargo,
lazyno puede encargarse del control de acceso ni del registro; solo realiza la creación diferida. - Si necesitas registro o comprobaciones de permisos, debes crear el objeto Proxy directamente.
¿Qué es exactamente el patrón Proxy de Swift?
Proxy significa intermediario.
Colocas delante del objeto que realiza el trabajo real un intermediario con la misma interfaz.
Desde fuera parece que llamas al objeto real, pero en realidad pasas por el intermediario.
El intermediario puede reenviar la solicitud sin más o hacer algo adicional, como comprobar permisos, registrar llamadas o retrasar la creación del objeto real.
Tiene tres usos representativos.
- Virtual Proxy: retrasar la creación de objetos pesados hasta que se utilicen
- Protection Proxy: impedir accesos no autorizados
- Logging Proxy: registrar el historial de llamadas
La protagonista de hoy es la primera opción, el Virtual Proxy. Es el punto que conecta exactamente con lazy.
La identidad del objeto intermediario oculto tras lazy
Pensemos en una situación en la que se manejan imágenes de alta resolución.
Cargar imágenes es costoso. Cargarlas todas por adelantado cuando ni siquiera se muestran desperdicia memoria.
De ahí surge la idea de cargarlas solo cuando realmente hagan falta. Ese es el núcleo del Virtual Proxy.
Una implementación directa con el patrón Proxy sería así.
protocol Image { func display() }
// Intermediario: retrasar la creación del objeto real hasta que sea necesario
final class ImageProxy: Image {
private let filename: String
private var real: RealImage? // Aún no creado
init(_ filename: String) { self.filename = filename }
func display() {
if real == nil { real = RealImage(filename) } // Se crea en este momento
real?.display()
}
}
display() solo se crea cuando se llama por primera vez a RealImage.
Hasta entonces, ImageProxy espera tranquilamente guardando solo el nombre del archivo. Ese es exactamente el trabajo del intermediario.
Pero Swift incorpora este patrón en su sintaxis. Es lazy.
final class Gallery {
// se crea una sola vez al acceder por primera vez a esta propiedad
lazy var cover: RealImage = RealImage("cover.png")
}
let g = Gallery() // aún no RealImage creado
g.cover.display() // se crea aquí por fin
Cuando se crea Gallery(), RealImage todavía no existe.
Se crea al acceder por primera vez a g.cover. El compilador se encarga de generar la creación diferida que antes realizaba ImageProxy.
Entonces, ¿si existe lazy ya no necesitamos el patrón Proxy?
Esa también fue mi primera duda.
La respuesta es no.
lazy solo se encarga de la creación diferida.
No puede bloquear accesos, registrar llamadas ni devolver objetos distintos según la condición.
He resumido la diferencia en una tabla.
| Elemento | Propiedad lazy | Proxy personalizado |
|---|---|---|
| Creación diferida | Sí | Sí |
| Comprobación de permisos de acceso | No | Sí |
| Registro y caché de llamadas | No | Sí |
| Cantidad de código | Una línea | Una clase |
| Reutilización | Limitada a esa propiedad | Reutilizable en varios lugares |
Por eso el criterio es sencillo.
Si solo necesitas «retrasar la creación», lazy una línea es la respuesta. No hace falta crear una clase.
En cambio, si necesitas intervenir con permisos, registro o una llamada remota, debes crear directamente un objeto Proxy.
Hay un punto importante que debes tener en cuenta.
lazy no garantiza la seguridad de los hilos.
Si varios hilos acceden por primera vez al mismo tiempo, la inicialización puede ejecutarse dos veces. En un entorno multihilo, es más seguro envolver esta parte en un Proxy personalizado y gestionar la sincronización.
Resumen de preguntas frecuentes
P. ¿En qué se diferencian lazy y una propiedad calculada?
lazy se calcula una sola vez y almacena el valor. Una propiedad calculada se recalcula cada vez que se accede a ella. Si quieres inicializar algo pesado una sola vez, debes usar lazy.
P. ¿Por qué lazy solo puede declararse como var?
Como la inicialización ocurre más tarde, existe brevemente una instancia cuyo valor aún no está definido. let no lo permite, por lo que lazy let es imposible sintácticamente.
En resumen, lazy es como un regalo en el que el lenguaje ha empaquetado de antemano el Virtual Proxy del patrón Proxy.
No hace falta crear una clase intermediaria para algo que cabe en una línea, pero entender el concepto que se oculta tras ella cambia claramente tu forma de leer el código.
A partir de hoy, cuando uses lazy var, piensa: «Ah, ahora estoy colocando un intermediario». El patrón te resultará mucho más cercano.

