Diseño de software

Patrón Prototype de Swift: NSCopying frente a la copia de tipos por valor

El patrón Prototype reduce el coste de creación y la duplicación de configuración al clonar objetos existentes. Aquí se resumen las diferencias entre la copia de tipos por valor de Swift y NSCopying en clases, además de cuándo intervienen la copia superficial y Copy-on-Write.

4 min de lectura
Imagen de portada de Patrón Prototype de Swift: NSCopying frente a la copia de tipos por valor

¿Alguna vez has estado copiando un objeto en Swift y te has detenido a pensar: «¿De verdad se ha copiado?»

Copiar una instancia de clase mediante una sola asignación suele provocar que modificar el clon también modifique el original.

Empecemos por la conclusión: en Swift, la copia tiene dos caminos.

Un tipo por valor (struct) se copia automáticamente al asignarlo, mientras que una clase (tipo por referencia) debe implementar NSCopying directamente para realizar una copia real.

En este artículo veremos qué es el patrón Prototype y, con código, en qué se diferencia NSCopying de la copia de tipos por valor.

Si primero necesitas conocer los límites de cada método de copia, consulta Copia superficial frente a copia profunda; para entender cómo las colecciones de Swift aplazan la copia real, consulta también Resumen de Copy-on-Write.


¿Qué es el patrón Prototype?

En pocas palabras, el patrón Prototype es un patrón de diseño que crea un objeto nuevo clonando uno existente.

En lugar de crearlo desde cero, copia un original ya configurado, como si lo estamparas.

A mí me ayuda compararlo con un sello. Si preparas bien un sello original, después solo tienes que estamparlo.

Resulta especialmente útil cuando crear objetos es costoso o la configuración inicial es compleja.

Por ejemplo, al crear un personaje de videojuego, puedes clonar un personaje base y modificarlo poco a poco en vez de configurar cada vez sus atributos, equipo y habilidades.

La clave es que el clon quede completamente separado del original.

Si al modificar el clon también cambia el original, no es una copia, sino una referencia compartida.

Aquí es donde se separan los tipos por valor y los tipos por referencia.


¿Por qué es cómoda la copia de tipos por valor?

Los struct y enum de Swift son tipos por valor.

La mayor ventaja de los tipos por valor es que se copian automáticamente incluso al asignarlos o pasarlos a una función.

El siguiente código debería dejarlo claro. Aunque cambies un valor después de copiar el original, no se afectan entre sí.

struct Character {
    var name: String
    var level: Int
}

var origin = Character(name: "Guerrero", level: 1)
var copy = origin      // El valor se copia por completo
copy.level = 99
print(origin.level)    // 1 (El original permanece igual!)

Aunque cambies copy.level a 99, origin.level sigue siendo 1.

Como no necesitas escribir código de copia adicional, se reducen mucho las posibilidades de cometer errores.

Por eso, salvo que haya una razón concreta, suelo considerar primero un struct.

Esta seguridad es precisamente el motivo por el que Swift recomienda copiar tipos por valor.


¿Cómo se usa NSCopying?

El problema son las clases, que son tipos por referencia.

Asignar una clase no la copia; solo comparte la dirección que apunta a la misma instancia.

Por eso, cuando necesitas una copia real, debes adoptar el NSCopying protocolo e implementar directamente el método copy(with:).

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: "Guerrero")
let clone = origin.copy() as! Character

Al llamar a copy() se crea una instancia nueva, así que aunque cambies clone, origin permanece igual.

Aquí hay un punto importante que debes tener en cuenta.

Para acercarte a una copia completa (copia profunda), dentro de copy(with:) también debes crear de nuevo las propiedades internas.

Si solo asignas los objetos internos tal cual, se copia la superficie, pero el contenido sigue compartido: es una copia superficial.


Copia de tipos por valor frente a NSCopying: comparación rápida

He resumido las diferencias en esta tabla.

Categoría Copia de tipo por valor (struct) NSCopying(class)
Método de copia Copia automática al asignar Llamar directamente a copy()
Requiere implementación No es necesario Requiere implementar copy(with:)
Comportamiento predeterminado Siempre una copia separada Comparte la dirección al asignar
Copia superficial/profunda Hay poco que tener en cuenta Debes gestionarlo directamente
Situación recomendada La mayoría de los modelos de datos Cuando necesitas un tipo por referencia

En resumen, diseñar con tipos por valor suele ser más fácil y seguro.

Sin embargo, usa una clase y NSCopying cuando necesites herencia, integración con una API de Objective-C o identidad de instancia.

En esos casos, implementar el patrón Prototype mediante NSCopying encaja perfectamente.

Suelo pensar primero en struct porque se copia con solo asignarlo
Suelo pensar primero en struct porque se copia con solo asignarlo

Preguntas frecuentes (Q&A)

P. Si solo uso structs, ¿puedo ignorar NSCopying?

Para la copia básica, sí. Pero al trabajar con UIKit o API basadas en Objective-C, puede que tengas que copiar clases, así que conviene conocer el concepto.

P. ¿Qué diferencia hay entre copy() y mutableCopy()?

copy() sirve para crear una copia inmutable y mutableCopy() para crear una copia modificable. Esta última requiere adoptar NSMutableCopying.

P. ¿La copia profunda es siempre la respuesta correcta?

No. Si se pueden compartir los objetos internos, una copia superficial puede ofrecer un mejor rendimiento.

Si preparas bien un sello original, después solo tienes que estamparlo
Si preparas bien un sello original, después solo tienes que estamparlo

Conclusión

Cuando la copia te confunda, pregúntate: «Si modifico el original, ¿también cambia el clon?»

Los tipos por valor dejan que Swift se encargue de esa preocupación; con las clases, debemos gestionarla nosotros mediante NSCopying.

Espero que el resumen de hoy te ayude a pasar un poco menos de tiempo atascado con las copias durante la noche. ¡Ánimo!