Você já parou ao copiar um objeto em Swift e pensou: “Isso foi realmente copiado?”
Copiar uma instância de classe com uma única atribuição costuma fazer com que alterar o clone também altere o original.
Começando pela conclusão: no Swift, a cópia segue dois caminhos.
Um tipo por valor (struct) é copiado automaticamente na atribuição, enquanto uma classe (tipo por referência) precisa implementar NSCopying diretamente para realizar uma cópia de verdade.
Neste artigo, vamos explicar o que é o padrão Prototype e, com código, como NSCopying difere da cópia de tipos por valor.
Se você precisa entender primeiro os limites de cada forma de cópia, veja Cópia rasa vs cópia profunda; para saber como as coleções do Swift adiam a cópia real, veja também Resumo de Copy-on-Write.
O que é o padrão Prototype?
Em uma frase, o padrão Prototype é um padrão de projeto que cria um novo objeto clonando um objeto existente.
Em vez de criar tudo do zero, você copia um original já configurado, como se estivesse carimbando cópias.
Eu gosto de comparar isso a um carimbo. Depois de preparar bem um carimbo original, basta carimbar as cópias.
Ele é especialmente útil quando o custo de criação do objeto é alto ou a configuração inicial é complexa.
Por exemplo, ao criar um personagem de jogo, você pode clonar um personagem-base e fazer pequenos ajustes, em vez de configurar atributos, equipamentos e habilidades toda vez.
O ponto principal é que o clone precisa ficar completamente separado do original.
Se alterar o clone também altera o original, isso não é cópia; é apenas compartilhamento de referência.
É exatamente aqui que os tipos por valor e por referência se diferenciam.
Por que a cópia de tipos por valor é conveniente?
Structs e enums do Swift são tipos por valor.
A maior vantagem dos tipos por valor é que eles são copiados automaticamente mesmo quando atribuídos ou passados para uma função.
O código abaixo deve deixar isso claro. Mesmo alterando um valor depois de copiar o original, um não afeta o outro.
struct Character {
var name: String
var level: Int
}
var origin = Character(name: "Guerreiro", level: 1)
var copy = origin // O valor é copiado por inteiro
copy.level = 99
print(origin.level) // 1 (O original permanece igual!)
Mesmo que você altere copy.level para 99, origin.level continua sendo 1.
Como não é preciso escrever um código de cópia separado, a chance de erros diminui bastante.
Por isso, salvo quando existe um motivo específico, costumo considerar um struct primeiro.
É justamente essa segurança que faz o Swift recomendar a cópia de tipos por valor.
Como usar NSCopying?
O problema são as classes, que são tipos por referência.
A atribuição de uma classe não faz uma cópia; ela apenas compartilha o endereço que aponta para a mesma instância.
Por isso, quando uma cópia real é necessária, você precisa adotar o NSCopying protocolo e implementar o método copy(with:) diretamente.
class Character: NSCopying {
var name: String
init(name: String) { self.name = name }
func copy(with zone: NSZone? = nil) -> Any {
return Character(name: self.name)
}
}
let origin = Character(name: "Guerreiro")
let clone = origin.copy() as! Character
Ao chamar copy(), uma nova instância é criada; portanto, mesmo que você altere clone, origin permanece igual.
Há um ponto importante a observar aqui.
Para chegar perto de uma cópia completa (cópia profunda), você também precisa criar novas propriedades internas dentro de copy(with:).
Se você apenas atribuir os objetos internos, a parte externa será copiada, mas o conteúdo continuará compartilhado: uma cópia rasa.
Cópia de tipos por valor vs NSCopying: comparação rápida
Organizei as diferenças entre os dois métodos nesta tabela.
| Categoria | Cópia de tipo por valor (struct) | NSCopying(class) |
|---|---|---|
| Método de cópia | Cópia automática na atribuição | Chamada direta de copy() |
| Requer implementação | Não é necessário | Requer implementação de copy(with:) |
| Comportamento padrão | Sempre uma cópia separada | Compartilhamento do endereço na atribuição |
| Cópia rasa/profunda | Pouco com que se preocupar | Você precisa cuidar disso diretamente |
| Quando usar | A maioria dos modelos de dados | Quando um tipo por referência é necessário |
Em resumo, projetar usando tipos por valor costuma ser mais fácil e seguro.
No entanto, use uma classe e NSCopying quando precisar de herança, integração com uma API Objective-C ou identidade de instância.
Nesses casos, implementar o padrão Prototype usando NSCopying se encaixa perfeitamente.
Perguntas frequentes (Q&A)
P. Se eu usar apenas structs, posso ignorar NSCopying?
Para cópias básicas, sim. Mas ao trabalhar com UIKit ou APIs baseadas em Objective-C, talvez você precise copiar classes, então vale conhecer o conceito.
P. Qual é a diferença entre copy() e mutableCopy()?
copy() cria uma cópia imutável, enquanto mutableCopy() cria uma cópia modificável. Esta última exige a adoção de NSMutableCopying.
P. A cópia profunda é sempre a resposta certa?
Não. Se for aceitável compartilhar objetos internos, uma cópia rasa pode oferecer melhor desempenho.
Conclusão
Quando a cópia parecer confusa, pense apenas: “Se eu alterar o original, o clone também muda?”
Os tipos por valor deixam o Swift cuidar dessa questão; com classes, nós mesmos precisamos gerenciá-la usando NSCopying.
Espero que o resumo de hoje ajude você a passar um pouco menos de tempo quebrando a cabeça com cópias durante a noite. Força!

