Ao desenvolver para iOS, você acaba usando lazy var com muita frequência.
Eu também era assim. Por hábito, usava isso para adiar a inicialização de objetos pesados.
Então surgiu uma dúvida: lazy não é, na prática, o mesmo Padrão Proxy dos livros de padrões de projeto?
Vamos começar pela conclusão.
lazyé a versão integrada à linguagem de um Virtual Proxy que adia a criação até o momento em que o objeto real é necessário.
Hoje vou explicar o que é o Padrão Proxy em Swift e como esse conceito se esconde atrás da palavra-chave lazy, seguindo a ordem em que o entendi ao trabalhar diretamente com o código.
Veja primeiro o resumo principal.
- O Padrão Proxy é um padrão estrutural que controla o acesso colocando um intermediário diante do objeto real.
lazytem o mesmo objetivo de um Virtual Proxy que adia a criação.- Porém,
lazynão consegue fazer controle de acesso ou logging; ele cuida apenas da criação tardia. - Se você precisa de logging ou verificação de permissões, deve criar o objeto proxy diretamente.
Afinal, o que é o Padrão Proxy do Swift?
Proxy significa intermediário.
Você coloca, diante do objeto que realiza o trabalho de verdade, um intermediário com a mesma interface.
Por fora, parece que você chama o objeto real, mas, na prática, passa pelo intermediário.
O intermediário pode simplesmente encaminhar a solicitação ou fazer algo a mais, como verificar permissões, registrar chamadas ou adiar a criação do objeto real.
Há três usos comuns.
- Virtual Proxy: adiar a criação de objetos pesados até o uso efetivo
- Protection Proxy: bloquear acessos não autorizados
- Logging Proxy: registrar o histórico de chamadas
O protagonista de hoje é o primeiro: o Virtual Proxy. É exatamente o ponto de contato com lazy.
A identidade do objeto intermediário oculto por trás de lazy
Vamos imaginar uma situação que lida com imagens em alta resolução.
Carregar imagens é pesado. Carregar tudo antecipadamente, quando nem aparece na tela, desperdiça memória.
Daí surge a ideia de carregar apenas quando for realmente necessário. Esse é o núcleo do Virtual Proxy.
Uma implementação direta usando o Padrão Proxy ficaria assim.
protocol Image { func display() }
// Intermediário: adiar a criação do objeto real até que seja necessário
final class ImageProxy: Image {
private let filename: String
private var real: RealImage? // Ainda não criado
init(_ filename: String) { self.filename = filename }
func display() {
if real == nil { real = RealImage(filename) } // Criado neste momento
real?.display()
}
}
display() só é criado quando é chamado pela primeira vez; nesse momento, RealImage é criado.
Até lá, ImageProxy espera tranquilamente carregando apenas o nome do arquivo. Esse é exatamente o trabalho do intermediário.
Mas o Swift incorporou esse padrão à própria sintaxe. É o lazy.
final class Gallery {
// é criado uma única vez no primeiro acesso a esta propriedade
lazy var cover: RealImage = RealImage("cover.png")
}
let g = Gallery() // ainda RealImage não criado
g.cover.display() // finalmente criado aqui
No momento em que Gallery() é criado, RealImage ainda não existe.
Ele é criado no primeiro acesso a g.cover. O compilador cria por você a criação tardia que ImageProxy fazia acima.
Então, se existe lazy, não precisamos do Padrão Proxy?
Essa também foi a minha primeira dúvida.
A resposta é não.
lazy faz apenas a criação tardia.
Ele não consegue bloquear acessos, registrar chamadas nem retornar objetos diferentes conforme a condição.
Resumi a diferença em uma tabela.
| Item | Propriedade lazy | Proxy criado manualmente |
|---|---|---|
| Criação tardia | Sim | Sim |
| Verificação de permissão de acesso | Não | Sim |
| Logging e cache de chamadas | Não | Sim |
| Quantidade de código | Uma linha | Uma classe |
| Reutilização | Limitada à propriedade | Reutilizável em vários lugares |
Por isso, o critério é simples.
Se você só precisa “adiar a criação”, lazy uma linha é a resposta. Não há motivo para criar uma classe.
Por outro lado, se houver permissões, logging ou uma chamada remota para encapsular, você precisa criar um objeto proxy diretamente.
Há um ponto importante de atenção.
lazy não garante segurança de thread.
Se várias threads acessarem pela primeira vez ao mesmo tempo, a inicialização poderá ocorrer duas vezes. Em um ambiente multithread, é mais seguro envolver essa parte em um proxy próprio e cuidar da sincronização.
Resumo das perguntas frequentes
P. Qual é a diferença entre lazy e uma computed property?
lazy é calculada uma única vez e armazena o valor. Uma computed property é recalculada a cada acesso. Se quiser fazer uma inicialização pesada apenas uma vez, lazy é a opção certa.
P. Por que lazy só pode ser declarado como var?
Como a inicialização ocorre depois, uma instância cujo valor ainda não foi definido existe brevemente. let não permite isso, então lazy let é impossível pela sintaxe.
Em resumo, lazy é como um presente no qual a linguagem já empacotou o Virtual Proxy do Padrão Proxy.
Não é preciso criar uma classe intermediária para algo que cabe em uma linha, mas entender o conceito escondido por trás dela muda claramente sua forma de ler o código.
A partir de hoje, ao usar lazy var, pense: “Ah, estou colocando um intermediário aqui.” O padrão parecerá muito mais próximo.

