Diseño de software

Patrón Flyweight en Swift: ahorra memoria compartiendo objetos

Al crear una aplicación, a veces hay que generar miles o decenas de miles de objetos similares. Un ejemplo típico es colocar decenas de miles de marcadores en un mapa; en estas situaciones, el gráfico de memoria puede seguir creciendo fácilmente.

4 min de lectura
Imagen de portada de Patrón Flyweight en Swift: ahorra memoria compartiendo objetos

Al crear una aplicación, a veces hay que generar miles o decenas de miles de objetos similares. Un ejemplo típico es colocar decenas de miles de marcadores en un mapa; en estas situaciones, el gráfico de memoria puede seguir creciendo fácilmente.

Aquí resulta útil el patrón Flyweight. En pocas palabras, combina y comparte los datos que usan varios objetos para reducir considerablemente el consumo de memoria. En lugar de crear lo mismo una y otra vez, se crea una vez y se reutiliza.

En este artículo veremos qué es el patrón Flyweight en Swift, cuándo conviene usarlo y cómo implementarlo con código real.

¿Qué es exactamente el patrón Flyweight?

Flyweight significa «objeto ligero». También es el nombre de la categoría más ligera del boxeo.

La idea es sencilla: dividir los datos de un objeto en dos tipos.

  • Estado intrínseco: datos inmutables que varios objetos pueden compartir, como la especie de un árbol, su textura o su color.
  • Estado extrínseco: datos que varían entre objetos y cambian según la situación, como la posición o el tamaño de un árbol.

Imaginemos que dibujamos un bosque. ¿Qué pasaría si hubiera que dibujar un millón de árboles, pero solo fueran de tres especies: pinos, robles y arces?

Basta con crear y compartir tres conjuntos de información de textura y color (estado intrínseco), y guardar por separado un millón de valores de posición (estado extrínseco).

Crear y compartir una sola vez los datos comunes pesados, y gestionar por separado únicamente los datos individuales ligeros. Eso es todo el patrón Flyweight.

Aquel día me pasé un buen rato lidiando con el pico de memoria provocado por colocar los marcadores.
Aquel día me pasé un buen rato lidiando con el pico de memoria provocado por colocar los marcadores.

Implementémoslo con código Swift

Solo con palabras cuesta hacerse una idea, así que veamos directamente el código. Trasladaremos tal cual el ejemplo de los árboles.

Primero, el objeto Flyweight que contiene el estado intrínseco que vamos a compartir.

// Datos pesados compartidos (estado intrínseco)
final class TreeType {
    let name: String
    let color: String
    let texture: Data  // Textura de gran tamaño

    init(name: String, color: String, texture: Data) {
        self.name = name
        self.color = color
        self.texture = texture
    }
}

A continuación creamos una fábrica que administra y comparte estos TreeType. Si un tipo ya existe, lo reutiliza en lugar de crear otro.

// Reutilizar el mismo tipo sin crearlo de nuevo
final class TreeFactory {
    private var pool: [String: TreeType] = [:]

    func treeType(name: String, color: String, texture: Data) -> TreeType {
        let key = "\(name)-\(color)"
        if let existing = pool[key] { return existing }
        let type = TreeType(name: name, color: color, texture: texture)
        pool[key] = type
        return type
    }
}

El árbol real solo conserva el estado extrínseco, como la posición, y se limita a referenciar los datos pesados mediante TreeType.

La fábrica conserva los datos y cada árbol lleva únicamente una referencia.
La fábrica conserva los datos y cada árbol lleva únicamente una referencia.
// Cada árbol solo guarda su posición y comparte el tipo mediante una referencia
struct Tree {
    let x: Double
    let y: Double
    let type: TreeType  // Referencia al objeto compartido
}

Aunque plantemos un millón de árboles, solo existirán tantas instancias de TreeType como especies; por ejemplo, tres. Todo lo demás son referencias a esas tres.

Incluso para dibujar todo el bosque, basta con compartir tres texturas.
Incluso para dibujar todo el bosque, basta con compartir tres texturas.

Cuándo usarlo y cuándo evitarlo

El patrón Flyweight no siempre es la respuesta correcta. Muchas veces es mejor no usarlo. Este es el resumen según la situación.

Situación Flyweight
Crear muchísimos objetos de la misma naturaleza Adecuado
Los objetos contienen datos pesados que se pueden compartir Adecuado
Pocos objetos Innecesario (solo aumenta la complejidad)
La mayor parte del estado es diferente en cada objeto Beneficio mínimo

El patrón Flyweight encaja cuando coinciden estas dos condiciones: muchos objetos y datos pesados que se pueden compartir.

En Swift, class ya se comparte por referencia al ser un tipo por referencia, por lo que resulta especialmente eficaz cuando un struct, que es un tipo por valor, contiene datos pesados y se copia repetidamente.


Preguntas frecuentes

P. ¿En qué se diferencia del patrón Singleton?

Singleton obliga a que una clase tenga una única instancia. Flyweight permite varias instancias, pero comparte solo lo que se puede compartir: por ejemplo, tres tipos de árbol y un millón de árboles.

P. Lo confundo con la caché.

El objetivo es distinto. La caché guarda resultados para no volver a calcularlos o consultarlos, mientras que Flyweight administra objetos compartidos para ahorrar memoria. Aun así, el pool de la fábrica funciona de forma parecida a una caché.

P. ¿También se usa en la Swift Standard Library?

Sí. La internación de cadenas y la caché de enteros pequeños son optimizaciones basadas en una idea similar: compartir valores idénticos en lugar de crearlos cada vez.


El nombre del patrón Flyweight puede parecer complicado, pero la idea es muy sencilla: «Crear una sola vez lo pesado que se repite y compartirlo».

Si tienes que manejar grandes cantidades de objetos similares, como marcadores de mapas, partículas, tile maps o renderizado de texto, conviene tenerlo presente. Verás cómo el gráfico de memoria se vuelve notablemente más estable.

Artículos relacionados