Copia profunda versus copia superficial, y cómo puede usarlas en Swift

Copiar un objeto siempre ha sido una parte esencial del paradigma de la codificación. Ya sea en Swift, Objective-C, JAVA o cualquier otro lenguaje, siempre necesitaremos copiar un objeto para usarlo en diferentes contextos.

En este artículo, discutiremos en detalle cómo copiar diferentes tipos de datos en Swift y cómo se comportan en diferentes circunstancias.

Tipos de valor y referencia

Todos los tipos de datos en Swift se dividen en dos categorías, a saber , tipos de valor y tipos de referencia .

  • Tipo de valor : cada instancia conserva una copia única de sus datos. Los tipos de datos que entran en esta categoría incluyen - all the basic data types, struct, enum, array, tuples.
  • Tipo de referencia : las instancias comparten una única copia de los datos, y el tipo generalmente se define como class.

La característica más distintiva de ambos tipos radica en su comportamiento de copia.

¿Qué es la copia profunda y superficial?

Una instancia, ya sea un tipo de valor o un tipo de referencia, se puede copiar de una de las siguientes formas:

Copia profunda: duplica todo

  • Con una copia profunda, cualquier objeto señalado por la fuente se copia y la copia está señalada por el destino. Entonces se crearán dos objetos completamente separados.
  • Colecciones : una copia profunda de una colección son dos colecciones con todos los elementos de la colección original duplicados.
  • Menos propenso a las condiciones de carrera y funciona bien en un entorno multiproceso: los cambios en un objeto no tendrán ningún efecto en otro objeto.
  • Los tipos de valor se copian profundamente.

En el código anterior,

  • Línea 1 : arr1- matriz (un tipo de valor) de cadenas
  • Línea 2 : arr1está asignada a arr2. Esto creará una copia profunda de arr1y luego asignará esa copia aarr2
  • Líneas 7 a 11 : los cambios realizados en arr2no se reflejan en arr1.

Esto es lo que es la copia profunda: instancias completamente separadas. El mismo concepto funciona con todos los tipos de valor.

En algunos escenarios, es decir, cuando un tipo de valor contiene tipos de referencia anidados, la copia profunda revela un tipo de comportamiento diferente. Lo veremos en las próximas secciones.

Copia superficial: duplica lo menos posible

  • Con una copia superficial, cualquier objeto apuntado por la fuente también será apuntado por el destino. Entonces, solo se creará un objeto en la memoria.
  • Colecciones : una copia superficial de una colección es una copia de la estructura de la colección, no de los elementos. Con una copia superficial, dos colecciones ahora comparten los elementos individuales.
  • Más rápido : solo se copia la referencia.
  • La copia de tipos de referencia crea una copia superficial.

En el código anterior,

  • Líneas 1 a 8 : Addresstipo de clase
  • Línea 10 : a1- una instancia de Addresstipo
  • Línea 11 : a1está asignada a a2. Esto creará una copia superficial a1y luego asignará esa copia a2, es decir, solo se copia la referencia a2.
  • Líneas 16 a 19 : cualquier cambio realizado a2ciertamente se reflejará en a1.

En la ilustración anterior, podemos ver que ambos a1y a2apuntan a la misma dirección de memoria.

Copiar tipos de referencia en profundidad

A partir de ahora, sabemos que siempre que intentamos copiar un tipo de referencia, solo se copia la referencia al objeto. No se crea ningún objeto nuevo. ¿Qué pasa si queremos crear un objeto completamente separado?

Podemos crear una copia profunda del tipo de referencia usando el copy()método. Según la documentación,

copy (): devuelve el objeto devuelto por copy(with:).

Este es un método conveniente para las clases que adoptan el NSCopyingprotocolo. Se genera una excepción si no hay implementación para copy(with:).

Reestructuremos el Address classque creamos en el fragmento de código 2 para que se ajuste al NSCopyingprotocolo.

En el código anterior,

  • Líneas 1 a 14 : el Addresstipo de clase se ajusta NSCopyinge implementa el copy(with:)método
  • Línea 16 : a1- una instancia de Addresstipo
  • Línea 17 : a1está asignada al método de a2uso copy(). Esto creará una copia profunda a1y luego asignará esa copia a a2, es decir, se creará un objeto completamente nuevo.
  • Líneas 22 a 25 : los cambios realizados en a2no se reflejarán en a1.

Como es evidente en la ilustración anterior, ambos a1y a2apuntan a diferentes ubicaciones de memoria.

Veamos otro ejemplo. Esta vez veremos cómo funciona con tipos de referencia anidados, un tipo de referencia que contiene otro tipo de referencia .

En el código anterior,

  • Línea 22:p1 se asigna una copia profunda de para p2usar el copy()método. Esto implica que cualquier cambio en uno de ellos no debe tener ningún efecto sobre el otro.
  • Líneas 27 a 28:p2’sname y cityse cambian los valores. Estos no deben reflejarse en p1.
  • Línea 30:p1’sname es como se esperaba, pero city¿ es ? Debería serlo, “Mumbai”¿no? Pero no podemos ver que eso suceda. “Bangalore”era solo por p2derecho? Sí ... exactamente.?

Copia profunda…! ? T sombrero no se esperaba de usted. Dijiste que copiarías todo. Y ahora te estás comportando así. Porque Oh porque..?! ¿Qué hago ahora? ☠

Que no cunda el pánico. Veamos lo que las direcciones de memoria tienen que decir en esto.

En la ilustración anterior, podemos ver que

  • p1y p2apunte a diferentes ubicaciones de memoria como se esperaba.
  • Pero sus addressvariables siguen apuntando a la misma ubicación. Esto significa que incluso después de copiarlos en profundidad, solo se copian las referencias, es decir, una copia superficial, por supuesto.

Tenga en cuenta: cada vez que copiamos un tipo de referencia, se crea una copia superficial de forma predeterminada hasta que especifiquemos explícitamente que debe copiarse en profundidad.

func copy(with zone: NSZone? = nil) -> Any{ let person = Person(self.name, self.address) return person}

En el método anterior que implementamos anteriormente para la Personclase, hemos creado una nueva instancia copiando la dirección con self.address. Esto solo copiará la referencia al objeto de dirección. Esta es la razón por la cual tanto p1y p2’saddressapuntan a la misma ubicación.

Por lo tanto, copiar el objeto usando el copy()método no creará una verdadera copia profunda del objeto .

Para duplicar un objeto de referencia por completo: el tipo de referencia junto con todos los tipos de referencia anidados deben copiarse con el copy()método.

let person = Person(self.name, self.address.copy() as? Address)

El uso del código anterior en el func copy(with zone: NSZone? = nil) ->método Any hará que todo funcione. Puede ver eso en la siguiente ilustración.

True Deep Copy: tipos de referencia y valor

Ya hemos visto cómo podemos crear una copia profunda de los tipos de referencia. Por supuesto que podemos hacer eso con todos los tipos de referencia anidados.

Pero, ¿qué pasa con el tipo de referencia anidado en un tipo de valor, que es una matriz de objetos o una variable de tipo de referencia en una estructura o tal vez una tupla? ¿Podemos resolver eso usando copy()también? No, no podemos, en realidad. El copy()método requiere implementar un NSCopyingprotocolo que solo funciona para NSObjectsubclases. Los tipos de valor no admiten la herencia, por lo que no podemos copy()usarlos.

En la línea 2, solo la estructura de arr1se copia en profundidad, pero los Addressobjetos dentro de ella todavía se copian en profundidad. Puede ver eso en el mapa de memoria a continuación.

Los elementos en ambos arr1y en arr2ambos apuntan a las mismas ubicaciones de memoria. Esto se debe a la misma razón: los tipos de referencia se copian superficialmente de forma predeterminada.

Serializar y luego deserializar un objeto siempre crea un objeto nuevo. Es válido tanto para los tipos de valores como para los tipos de referencia.

Aquí hay algunas API que podemos usar para serializar y deserializar datos:

  1. NSCoding : protocolo que permite codificar y decodificar un objeto para su archivo y distribución. Solo funcionará con classobjetos de tipo ya que requiere heredar NSObject.
  2. Codificable : haga que sus tipos de datos sean codificables y decodificables para que sean compatibles con representaciones externas como JSON. Funcionará para ambos tipos de valores, struct, array, tuple, basic data typesasí como para los tipos de referencia class.

Reestructuraremos la Addressclase un poco más para cumplir con el Codableprotocolo y eliminemos todo el NSCopyingcódigo que agregamos anteriormente en el fragmento de código 3.

En el código anterior, las líneas 11-13 crearán una verdadera copia profunda de arr1. A continuación se muestra la ilustración que ofrece una imagen clara de las ubicaciones de la memoria.

Copiar en escrito

Copiar al escribir es una técnica de optimización que ayuda a mejorar el rendimiento al copiar tipos de valor.

Digamos que copiamos un solo String o Int o tal vez cualquier otro tipo de valor; no enfrentaremos ningún problema de rendimiento crucial en ese caso. Pero, ¿qué pasa cuando copiamos una matriz de miles de elementos? ¿Seguirá sin crear problemas de rendimiento? ¿Qué pasa si simplemente lo copiamos y no hacemos ningún cambio en esa copia? ¿No es esa memoria extra que usamos solo un desperdicio en ese caso?

Aquí viene el concepto de Copiar en Escritura: al copiar, cada referencia apunta a la misma dirección de memoria. Solo cuando una de las referencias modifica los datos subyacentes, Swift realmente copia la instancia original y realiza la modificación.

Es decir, ya sea una copia profunda o una copia superficial, no se creará una nueva copia hasta que hagamos un cambio en uno de los objetos.

En el código anterior,

  • Línea 2 : arr1se asigna una copia profunda dearr2
  • Líneas 4 y 5 : arr1y arr2aún apuntan a la misma dirección de memoria
  • Línea 7 : cambios realizados enarr2
  • Líneas 9 y 10 : arr1y arr2ahora apuntando a diferentes ubicaciones de memoria

Ahora sabe más sobre copias profundas y superficiales y cómo se comportan en diferentes escenarios con diferentes tipos de datos. Puede probarlos con su propio conjunto de ejemplos y ver qué resultados obtiene.

Otras lecturas

No olvide leer mis otros artículos:

  1. Todo sobre Codable en Swift 4
  2. Todo lo que siempre quiso saber sobre las notificaciones en iOS
  3. Coloréalo con GRADIENTS - iOS
  4. Codificación para iOS 11: cómo arrastrar y soltar en colecciones y tablas
  5. Todo lo que necesita saber sobre las extensiones Today (widget) en iOS 10
  6. La selección de UICollectionViewCell se hizo fácil .. !!

No dude en dejar comentarios en caso de que tenga alguna pregunta.