par Payal Gupta

La copie d’un objet a toujours été un élément essentiel du paradigme du codage. Que ce soit en Swift, Objective-C, JAVA ou tout autre langage, nous aurons toujours besoin de copier un objet pour l’utiliser dans différents contextes.

Dans cet article, nous expliquerons en détail comment copier différents types de données dans Swift et comment ils se comportent dans différentes circonstances.

Types de valeur et de référence

Tous les types de données de Swift se divisent généralement en deux catégories, à savoir les types de valeur et les types de référence.

  • Type de valeur – chaque instance conserve une copie unique de ses données. Les types de données entrant dans cette catégorie incluent —all the basic data types, struct, enum, array, tuples.
  • Type de référence — les instances partagent une seule copie des données, et le type est généralement défini comme un class.

La caractéristique la plus distinctive des deux types réside dans leur comportement de copie.

Qu’est-ce qu’une copie profonde et superficielle?

Une instance, qu’il s’agisse d’un type de valeur ou d’un type de référence, peut être copiée de l’une des manières suivantes :

Copie profonde — Duplique tout

  • Avec une copie profonde, tout objet pointé par la source est copié et la copie est pointée par la destination. Ainsi, deux objets complètement séparés seront créés.
  • Collections – Une copie profonde d’une collection est deux collections avec tous les éléments de la collection originale dupliqués.
  • Moins sujet aux conditions de course et fonctionne bien dans un environnement multithread — les changements dans un objet n’auront aucun effet sur un autre objet.Les types de valeurs
  • sont copiés en profondeur.

Dans le code ci-dessus,

  • Ligne 1:
  • La ligne 2: arr1 est affectée à arr2. Cela créera une copie profonde de arr1, puis affectera cette copie à arr2
  • Lignes 7 à 11: toutes les modifications effectuées dans arr2 ne se reflètent pas dans arr1.

C’est ce qu’est la copie profonde — des instances complètement séparées. Le même concept fonctionne avec tous les types de valeurs.

Dans certains scénarios, c’est-à-dire lorsqu’un type de valeur contient des types de référence imbriqués, la copie profonde révèle un comportement différent. Nous verrons cela dans les prochaines sections.

Copie superficielle – Duplique le moins possible

  • Avec une copie superficielle, tout objet pointé par la source est également pointé par la destination. Donc, un seul objet sera créé dans la mémoire.
  • Collections – Une copie superficielle d’une collection est une copie de la structure de la collection, pas des éléments. Avec une copie superficielle, deux collections partagent désormais les éléments individuels.
  • Plus rapide – seule la référence est copiée.
  • La copie des types de référence crée une copie superficielle.

Dans le code ci-dessus,

  • Lignes 1 à 8 : Address type de classe
  • Ligne 10 : a1 — une instance de Address type
  • Ligne 11 : a1 est affectée à a2. Cela créera une copie superficielle de a1, puis affectera cette copie à a2, c’est-à-dire que seule la référence est copiée dans a2.
  • Lignes 16 à 19: toutes les modifications apportées à a2 se refléteront certainement dans a1.

Dans l’illustration ci-dessus, nous pouvons voir que a1 et a2 pointent tous deux vers la même adresse mémoire.

Copier profondément les types de référence

À partir de maintenant, nous savons que chaque fois que nous essayons de copier un type de référence, seule la référence à l’objet est copiée. Aucun nouvel objet n’est créé. Et si nous voulons créer un objet complètement séparé?

Nous pouvons créer une copie profonde du type de référence en utilisant la méthode copy(). Selon la documentation,

copy() – Renvoie l’objet renvoyé par copy(with:).

Il s’agit d’une méthode pratique pour les classes qui adoptent le protocole NSCopying. Une exception est levée s’il n’y a pas d’implémentation pour copy(with:).

Restructurons le Address class que nous avons créé dans l’extrait de code 2 pour se conformer au protocole NSCopying.

Dans le code ci-dessus,

  • Lignes 1 à 14 : Address le type de classe est conforme à NSCopying et implémente copy(with:) méthode
  • Ligne 16 : Address type
  • Ligne 17: a1 est affecté à a2 à l’aide de la méthode copy(). Cela créera une copie profonde de a1, puis affectera cette copie à a2, c’est-à-dire qu’un tout nouvel objet sera créé.
  • Lignes 22 à 25 : toute modification effectuée dans a2 ne sera pas prise en compte dans a1.

Comme il ressort de l’illustration ci-dessus, a1 et a2 pointent vers des emplacements de mémoire différents.

Regardons un autre exemple. Cette fois, nous verrons comment cela fonctionne avec les types de référence imbriqués — un type de référence contenant un autre type de référence.

Dans le code ci-dessus,

  • Ligne 22 : une copie profonde de p1 est affectée à p2 à l’aide de la méthode copy(). Cela implique que tout changement dans l’un d’eux ne doit avoir aucun effet sur l’autre.
  • Lignes 27 à 28 : les valeurs p2's name et city sont modifiées. Ceux-ci ne doivent pas refléter dans p1.
  • Ligne 30: p1's name est comme prévu, mais son city? Ça devrait être "Mumbai" n’est-ce pas? Mais nous ne pouvons pas voir cela se produire. "Bangalore" était seulement pour p2 non? Ouaisexactly exactement.?

Copie profonde…!? On ne s’attendait pas à ça de votre part. Tu as dit que tu copierais tout. Et maintenant, vous vous comportez comme ça. Pourquoi oh pourquoi..?! Qu’est-ce que je fais maintenant? ️️

Ne paniquez pas. Regardons ce que les adresses de mémoire ont à dire dans cela.

De l’illustration ci-dessus, nous pouvons voir que

  • p1 et p2 pointent vers différents emplacements de mémoire comme prévu.
  • Mais leurs variables address pointent toujours vers le même emplacement. Cela signifie que même après les avoir copiées profondément, seules les références sont copiées — c’est-à-dire une copie superficielle bien sûr.

Veuillez noter: chaque fois que nous copions un type de référence, une copie superficielle est créée par défaut jusqu’à ce que nous spécifiions explicitement qu’elle doit être copiée en profondeur.

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

Dans la méthode ci-dessus que nous avons implémentée précédemment pour la classe Person, nous avons créé une nouvelle instance en copiant l’adresse avec self.address. Cela copiera uniquement la référence à l’objet adresse. C’est la raison pour laquelle p1 et p2's address pointent tous les deux vers le même emplacement.

Ainsi, la copie de l’objet à l’aide de la méthode copy() ne créera pas de véritable copie profonde de l’objet.

Pour dupliquer complètement un objet de référence: le type de référence ainsi que tous les types de référence imbriqués doivent être copiés avec la méthode copy().

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

En utilisant le code ci-dessus dans la méthode func copy(with zone: NSZone? = nil) -> , tout fonctionnera. Vous pouvez le voir sur l’illustration ci-dessous.

True Deep Copy – Types de référence et de valeur

Nous avons déjà vu comment nous pouvons créer une copie profonde des types de référence. Bien sûr, nous pouvons le faire avec tous les types de référence imbriqués.

Mais qu’en est-il du type de référence imbriqué dans un type de valeur, c’est-à-dire un tableau d’objets, ou une variable de type de référence dans une structure ou peut-être un tuple? Pouvons-nous résoudre cela en utilisant copy() aussi? Non, on ne peut pas, en fait. La méthode copy() nécessite l’implémentation du protocole NSCopying qui ne fonctionne que pour les sous-classes NSObject. Les types de valeurs ne prennent pas en charge l’héritage, nous ne pouvons donc pas utiliser copy() avec eux.

À la ligne 2, seule la structure de arr1 est copiée en profondeur, mais les objets Address à l’intérieur sont toujours copiés en profondeur. Vous pouvez le voir sur la carte mémoire ci-dessous.

Les éléments de arr1 et arr2 pointent tous les deux vers les mêmes emplacements de mémoire. C’est pour la même raison — les types de référence sont copiés superficiellement par défaut.

La sérialisation puis la désérialisation d’un objet crée toujours un nouvel objet. Il est valable pour les deux types de valeurs ainsi que pour les types de référence.

Voici quelques API que nous pouvons utiliser pour sérialiser et désérialiser les données:

  1. NSCoding – Un protocole qui permet à un objet d’être codé et décodé pour l’archivage et la distribution. Il ne fonctionnera qu’avec des objets de type class car il nécessite d’hériter de NSObject.
  2. Codable – Rendez vos types de données encodables et décodables pour une compatibilité avec des représentations externes telles que JSON. Cela fonctionnera pour les deux types de valeur – struct, array, tuple, basic data types ainsi que pour les types de référence — class.

Restructurons un peu plus la classe Address pour se conformer au protocole Codable et supprimons tout le code NSCopying que nous avons ajouté précédemment dans l’extrait de code 3.

Dans le code ci-dessus, les lignes 11 à 13 créeront une véritable copie profonde de arr1. Vous trouverez ci-dessous l’illustration qui donne une image claire des emplacements de la mémoire.

Copy on Write

Copy on write est une technique d’optimisation qui aide à améliorer les performances lors de la copie de types de valeurs.

Disons que nous copions une seule chaîne ou Int ou peut—être tout autre type de valeur – nous ne rencontrerons aucun problème de performance crucial dans ce cas. Mais qu’en est-il lorsque nous copions un tableau de milliers d’éléments? Cela ne créera-t-il toujours pas de problèmes de performances? Et si nous le copions et n’apportons aucune modification à cette copie? Cette mémoire supplémentaire que nous avons utilisée n’est-elle pas qu’un gaspillage dans ce cas?

Voici le concept de Copie en écriture — lors de la copie, chaque référence pointe vers la même adresse mémoire. Ce n’est que lorsque l’une des références modifie les données sous-jacentes que Swift copie réellement l’instance d’origine et effectue la modification.

Autrement dit, qu’il s’agisse d’une copie profonde ou d’une copie superficielle, une nouvelle copie ne sera pas créée tant que nous n’aurons pas modifié l’un des objets.

Dans le code ci-dessus,

  • Ligne 2 : une copie profonde de arr1 est affectée à arr2
  • Lignes 4 et 5: arr1 et arr2 pointent toujours vers la même adresse mémoire
  • Ligne 7: modifications apportées aux arr2
  • Lignes 9 et 10: arr1 et arr2 pointant désormais vers différents emplacements de mémoire

Vous en savez maintenant plus sur les copies profondes et peu profondes et leur comportement dans différents scénarios avec différents types de données. Vous pouvez les essayer avec votre propre ensemble d’exemples et voir quels résultats vous obtenez.

Lectures complémentaires

N’oubliez pas de lire mes autres articles:

  1. Tout sur Codable dans Swift 4
  2. Tout ce que vous avez toujours voulu savoir sur les notifications dans iOS
  3. Colorez—le avec des DÉGRADÉS – iOS
  4. Codage pour iOS 11: Comment faire glisser & déposer dans des collections & tables
  5. Tout ce que vous devez savoir sur les extensions d’aujourd’hui (Widget ) dans iOS 10
  6. Sélection UICollectionViewCell facilitée..!!

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.