de Payal Gupta

Copiar un objeto siempre ha sido una parte esencial en el paradigma de 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, analizaremos 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 de 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 sola copia de los datos, y el tipo se define generalmente 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 maneras:

Copia profunda: Duplica todo

  • Con una copia profunda, se copia cualquier objeto al que apunte el origen y el destino apunta a la copia. Así se crearán dos objetos completamente separados.Colecciones
  • : Una copia profunda de una colección es dos colecciones con todos los elementos de la colección original duplicados.
  • Menos propenso a condiciones de carrera y funciona bien en un entorno de varios hilos: los cambios en un objeto no tendrán efecto en otro objeto.
  • Los tipos de valor se copian profundamente.

En el código anterior,

  • Línea 1: arr1 – array (un tipo de valor) de cadenas
  • Línea 2: arr1 se asigna a arr2. Esto creará una copia profunda de arr1 y luego asignará esa copia a arr2
  • Líneas 7 a 11: cualquier cambio realizado en arr2 no se refleja 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 es apuntado por el destino. Así que 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.
  • Copiar tipos de referencia crea una copia superficial.

En el código anterior,

  • Líneas 1 a 8: Address tipo de clase
  • Línea 10: a1 – una instancia de Address tipo
  • Línea 11: a1 se asigna a a2. Esto creará una copia superficial de a1 y luego asignará esa copia a a2, es decir, solo la referencia se copia en a2.
  • Líneas 16 a 19: cualquier cambio realizado en a2 sin duda se reflejará en a1 .

En la ilustración anterior, podemos ver que tanto a1 como a2 apuntan a la misma dirección de memoria.

Copiar profundamente Tipos de referencia

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

Podemos crear una copia profunda del tipo de referencia utilizando el método copy(). De acuerdo con la documentación,

copy() — Devuelve el objeto devuelto por copy(with:).

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

Reestructuremos el Address class que creamos en el fragmento de código 2 para que se ajuste al protocolo NSCopying.

En el código anterior,

  • Líneas 1 a 14: Address tipo de clase conforme a NSCopying e implementa copy(with:) método
  • Línea 16: a1 – una instancia de Address tipo
  • Línea 17: a1 se asigna a a2 utilizando el método copy(). Esto creará una copia profunda de a1 y luego asignará esa copia a a2, es decir, se creará un objeto completamente nuevo.
  • Líneas 22 a 25: cualquier cambio realizado en a2 no se reflejará en a1 .

Como es evidente en la ilustración anterior, tanto a1 como a2 apuntan 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: se asigna una copia profunda de p1 a p2 utilizando el método copy(). Esto implica que cualquier cambio en uno de ellos no debe tener ningún efecto en el otro.
  • Líneas 27 a 28: p2's name y city se cambian los valores. Estos no deben reflejarse en p1.
  • Línea 30: p1's name es como se esperaba, pero es city? Debería ser "Mumbai" ¿No es así? Pero no podemos ver que eso suceda. "Bangalore" fue solo para p2 ¿verdad? Sí exactly exactamente.?

copia Profunda…!? Eso no se esperaba de ti. Dijiste que copiarías todo. Y ahora te comportas así. Por qué oh por qué..?! ¿Qué hago ahora? ☠️

No te asustes. Veamos lo que las direcciones de memoria tienen que decir en esto.

De la ilustración anterior, podemos ver que

  • p1 y p2 apunta a diferentes ubicaciones de memoria como se esperaba.
  • Pero sus variables address siguen apuntando a la misma ubicación. Esto significa que incluso después de copiarlos profundamente, 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 especificamos explícitamente que se debe copiar profundamente.

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 clase Person, hemos creado una nueva instancia copiando la dirección con self.address . Esto solo copiará la referencia al objeto address. Esta es la razón por la que p1 y p2's address apuntan a la misma ubicación.

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

Para duplicar un objeto de referencia completamente: el tipo de referencia junto con todos los tipos de referencia anidados se deben copiar con el método copy().

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

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

Copia profunda verdadera: Tipos de referencia y valor

Ya hemos visto cómo podemos crear una copia profunda de los tipos de referencia. Por supuesto, podemos hacerlo 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 podemos, en realidad. El método copy() requiere implementar el protocolo NSCopying que solo funciona para subclases NSObject. Los tipos de valor no admiten herencia, por lo que no podemos usar copy() con ellos.

En la línea 2, solo la estructura de arr1 se copia en profundidad, pero los objetos Address dentro de ella siguen siendo copiados en profundidad. Se puede ver en el mapa de memoria a continuación.

Los elementos de arr1 y arr2 apuntan a las mismas ubicaciones de memoria. Esto se debe a la misma razón: los tipos de referencia se copian de forma superficial de forma predeterminada.

Serializar y luego deserializar un objeto siempre crea un objeto nuevo. Es válido tanto para los tipos de valor 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 archivarlo y distribuirlo. Solo funcionará con objetos de tipo class, ya que requiere heredar de NSObject .
  2. Codable: Haga que sus tipos de datos sean codificables y decodificables para que sean compatibles con representaciones externas, como JSON. Funcionará tanto para los tipos de valor — struct, array, tuple, basic data typescomo para los tipos de referencia — class .

Reestructuremos la clase Address un poco más para ajustarse al protocolo Codable y eliminemos todo el código NSCopying 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 da una imagen clara de las ubicaciones de la memoria.

Copiar al escribir

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

Digamos que copiamos una sola cadena 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? ¿Todavía no creará ningún problema de rendimiento? ¿Y si simplemente lo copiamos y no hacemos ningún cambio en esa copia? ¿No es esa memoria extra que usamos 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 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 realicemos un cambio en uno de los objetos.

En el código anterior,

  • Línea 2: se asigna una copia profunda de arr1 a arr2
  • Líneas 4 y 5: arr1 y arr2 siguen apuntando a la misma dirección de memoria
  • Línea 7: cambios realizados en arr2
  • Líneas 9 y 10: arr1 y arr2 ahora apuntan 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. Puedes probarlos con tu propio conjunto de ejemplos y ver qué resultados obtienes.

Lectura adicional

No olvides leer mis otros artículos:

  1. Todo sobre Codable en Swift 4
  2. Todo lo que siempre has querido saber sobre las notificaciones en iOS
  3. Coloréalo con DEGRADADOS — iOS
  4. Codificación para iOS 11: Cómo arrastrar & soltar en colecciones & tablas
  5. Todo lo que necesitas saber sobre las extensiones de hoy (Widget) en iOS 10
  6. UICollectionViewCell selección fácil..!!

Deja una respuesta

Tu dirección de correo electrónico no será publicada.