Design de software

Padrão Flyweight no Swift: economize memória compartilhando objetos

Ao criar um app, às vezes é preciso gerar milhares ou dezenas de milhares de objetos semelhantes. Um exemplo típico é colocar dezenas de milhares de marcadores em um mapa; nessas situações, o gráfico de memória pode continuar subindo facilmente.

4 min de leitura
Imagem de capa de Padrão Flyweight no Swift: economize memória compartilhando objetos

Ao criar um app, às vezes é preciso gerar milhares ou dezenas de milhares de objetos semelhantes. Um exemplo típico é colocar dezenas de milhares de marcadores em um mapa; nessas situações, o gráfico de memória pode continuar subindo facilmente.

É aí que o padrão Flyweight pode ajudar. Em resumo, ele combina e compartilha os dados usados por vários objetos para reduzir bastante o uso de memória. Em vez de criar a mesma coisa repetidamente, você cria uma vez e reutiliza.

Neste artigo, vamos ver o que é o padrão Flyweight no Swift, quando usá-lo e como implementá-lo com código real.

O que é exatamente o padrão Flyweight?

Flyweight significa “objeto leve”. Também é o nome da categoria mais leve do boxe.

A ideia é simples: dividir os dados de um objeto em dois tipos.

  • Estado intrínseco: dados imutáveis que vários objetos podem compartilhar, como a espécie, a textura ou a cor de uma árvore.
  • Estado extrínseco: dados que variam entre os objetos e mudam conforme a situação, como a posição ou o tamanho de uma árvore.

Imagine que estamos desenhando uma floresta. E se precisássemos desenhar 1 milhão de árvores, mas elas fossem apenas de três espécies: pinheiro, carvalho e bordo?

Basta criar e compartilhar três conjuntos de informações de textura e cor (estado intrínseco) e manter separados 1 milhão de valores de posição (estado extrínseco).

Criar e compartilhar uma única vez os dados comuns pesados e gerenciar separadamente apenas os dados individuais leves. Isso é todo o padrão Flyweight.

Naquele dia, passei um bom tempo lidando com o pico de memória causado por colocar os marcadores.
Naquele dia, passei um bom tempo lidando com o pico de memória causado por colocar os marcadores.

Vamos implementar com código Swift

Só com palavras é difícil visualizar, então vamos direto ao código. Vamos transportar exatamente o exemplo das árvores.

Primeiro, o objeto Flyweight que armazena o estado intrínseco a ser compartilhado.

// Dados pesados compartilhados (estado intrínseco)
final class TreeType {
    let name: String
    let color: String
    let texture: Data  // Textura grande

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

Em seguida, criamos uma fábrica que gerencia e compartilha esses TreeType. Se um tipo já foi criado, ele é reutilizado em vez de ser criado novamente.

// Reutilizar o mesmo tipo sem criá-lo novamente
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
    }
}

Agora, a árvore real armazena apenas o estado extrínseco, como a posição, e apenas referencia os dados pesados por meio de TreeType.

A fábrica mantém os dados, enquanto cada árvore carrega apenas uma referência.
A fábrica mantém os dados, enquanto cada árvore carrega apenas uma referência.
// Cada árvore armazena apenas a posição e compartilha o tipo por referência
struct Tree {
    let x: Double
    let y: Double
    let type: TreeType  // Referência ao objeto compartilhado
}

Mesmo plantando 1 milhão de árvores, haverá apenas tantas instâncias de TreeType quanto espécies — por exemplo, três. Todo o restante são apenas referências a essas três.

Mesmo desenhando a floresta inteira, basta compartilhar três texturas.
Mesmo desenhando a floresta inteira, basta compartilhar três texturas.

Quando usar e quando não usar?

O padrão Flyweight nem sempre é a resposta certa. Em muitos casos, é melhor não usá-lo. Veja o resumo por situação.

Situação Flyweight
Criar muitos objetos com a mesma natureza Adequado
Os objetos têm dados pesados que podem ser compartilhados Adequado
Poucos objetos Desnecessário (apenas aumenta a complexidade)
A maior parte do estado é diferente em cada objeto Benefício mínimo

O padrão Flyweight se encaixa quando estas duas condições coincidem: muitos objetos e dados pesados que podem ser compartilhados.

No Swift, class já é compartilhada por referência por ser um tipo por referência. Por isso, o padrão é especialmente eficaz quando um struct, que é um tipo por valor, contém dados pesados e é copiado repetidamente.


Perguntas frequentes

P. Qual é a diferença em relação ao padrão Singleton?

Singleton força uma classe a ter exatamente uma instância. Flyweight permite várias instâncias, mas compartilha apenas o que pode ser compartilhado — por exemplo, três tipos de árvore e 1 milhão de árvores.

P. Eu confundo com cache.

O objetivo é diferente. O cache armazena resultados para evitar recalculá-los ou consultá-los novamente, enquanto o Flyweight gerencia objetos compartilhados para economizar memória. Ainda assim, o pool da fábrica funciona de modo parecido com um cache.

P. Ele também é usado na Swift Standard Library?

Sim. Internação de strings e cache de inteiros pequenos são otimizações baseadas em uma ideia semelhante: compartilhar valores idênticos em vez de criá-los sempre.


O nome padrão Flyweight pode parecer complicado, mas a ideia é bem simples: “Crie uma vez o que é pesado e se repete, e compartilhe.”

Se você precisa lidar com muitos objetos semelhantes, como marcadores de mapa, partículas, tile maps ou renderização de texto, vale lembrar dele. Você verá o gráfico de memória ficar visivelmente mais estável.

Artigos relacionados