Copie profonde d'un NSArray

119

Existe-t-il une fonction intégrée qui me permet de copier en profondeur un NSMutableArray?

J'ai regardé autour de moi, certaines personnes disent que cela [aMutableArray copyWithZone:nil]fonctionne comme une copie profonde. Mais j'ai essayé et cela semble être une copie superficielle.

En ce moment, je fais manuellement la copie avec une forboucle:

//deep copy a 9*9 mutable array to a passed-in reference array

-deepMuCopy : (NSMutableArray*) array 
    toNewArray : (NSMutableArray*) arrayNew {

    [arrayNew removeAllObjects];//ensure it's clean

    for (int y = 0; y<9; y++) {
        [arrayNew addObject:[NSMutableArray new]];
        for (int x = 0; x<9; x++) {
            [[arrayNew objectAtIndex:y] addObject:[NSMutableArray new]];

            NSMutableArray *aDomain = [[array objectAtIndex:y] objectAtIndex:x];
            for (int i = 0; i<[aDomain count]; i++) {

                //copy object by object
                NSNumber* n = [NSNumber numberWithInt:[[aDomain objectAtIndex:i] intValue]];
                [[[arrayNew objectAtIndex:y] objectAtIndex:x] addObject:n];
            }
        }
    }
}

mais j'aimerais une solution plus propre et plus succincte.

Ivan le Terrible
la source
44
Les copies profondes et superficielles @Genericrich sont des termes assez bien définis dans le développement de logiciels. Google.com peut vous aider
Andrew Grant
1
peut-être qu'une partie de la confusion est due au fait que le comportement des -copycollections immuables a changé entre Mac OS X 10.4 et 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (faites défiler jusqu'à "Collections immuables et comportement de copie")
user102008
1
@AndrewGrant Après réflexion, et avec respect, je ne suis pas d'accord pour dire que la copie profonde est un terme bien défini. Selon la source que vous lisez, il est difficile de savoir si une récursivité illimitée dans les structures de données imbriquées est une exigence d'une opération de `` copie complète ''. En d'autres termes, vous obtiendrez des réponses contradictoires quant à savoir si une opération de copie qui crée un nouvel objet dont les membres sont des copies superficielles des membres de l'objet d'origine est une opération de «copie complète» ou non. Voir stackoverflow.com/a/6183597/1709587 pour une discussion à ce sujet (dans un contexte Java, mais c'est quand même pertinent).
Mark Amery
@AndrewGrant Je dois sauvegarder @MarkAmery et @Genericrich. Une copie complète est bien définie si la classe racine utilisée dans une collection et tous ses éléments sont copiables. Ce n'est pas le cas avec NSArray (et d'autres collections objc). Si un élément n'est pas implémenté copy, que doit-on mettre dans la "copie complète"? Si l'élément est une autre collection, copyne produit pas réellement une copie (de la même classe). Je pense donc qu'il est parfaitement valable d'argumenter sur le type de copie souhaité dans le cas particulier.
Nikolai Ruhe
@NikolaiRuhe Si un élément n'implémente pas NSCopying/ -copy, alors il n'est pas copiable - vous ne devriez donc jamais essayer d'en faire une copie, car ce n'est pas une capacité pour laquelle il a été conçu. En termes d'implémentation de Cocoa, les objets non copiables ont souvent un état de backend C auquel ils sont liés, donc le piratage d'une copie directe de l'objet peut conduire à des conditions de concurrence ou pire. Donc, pour répondre «ce qui doit être mis dans la« copie profonde »» - Une réf. La seule chose que vous pouvez mettre n'importe où quand vous avez un non- NSCopyingobjet.
Slipp D.Thompson

Réponses:

210

Comme l' indique explicitement la documentation Apple sur les copies complètes:

Si vous n'avez besoin que d' une copie approfondie à un niveau :

NSMutableArray *newArray = [[NSMutableArray alloc] 
                             initWithArray:oldArray copyItems:YES];

Le code ci-dessus crée un nouveau tableau dont les membres sont des copies superficielles des membres de l'ancien tableau.

Notez que si vous devez copier en profondeur une structure de données imbriquée entière - ce que les documents Apple liés appellent une véritable copie profonde - alors cette approche ne suffira pas. Veuillez consulter les autres réponses ici pour cela.

François P.
la source
Semble être la bonne réponse. L'API indique que chaque élément reçoit un message [element copyWithZone:], qui peut être ce que vous voyiez. Si vous voyez en fait que l'envoi de [NSMutableArray copyWithZone: nil] ne copie pas en profondeur, alors un tableau de tableaux peut ne pas copier correctement en utilisant cette méthode.
Ed Marty
7
Je ne pense pas que cela fonctionnera comme prévu. Extrait de la documentation Apple: "La méthode copyWithZone: effectue une copie superficielle . Si vous avez une collection de profondeur arbitraire, le fait de passer YES pour le paramètre flag effectuera une copie immuable du premier niveau sous la surface. Si vous ne transmettez PAS la mutabilité de Le premier niveau n'est pas affecté. Dans les deux cas, la mutabilité de tous les niveaux plus profonds n'est pas affectée. »La question SO concerne les copies profondément mutables.
Joe D'Andrea
7
ce PAS une copie profonde
Daij-Djan
9
Ceci est une réponse incomplète. Il en résulte une copie profonde à un niveau. S'il existe des types plus complexes dans le tableau, il ne fournira pas de copie complète.
Cameron Lowell Palmer
7
Cela peut être une copie complète, selon la façon dont copyWithZone:est implémentée sur la classe de réception.
devios1
62

La seule façon que je connais de le faire facilement est d'archiver puis de désarchiver immédiatement votre tableau. Cela ressemble un peu à un hack, mais est en fait explicitement suggéré dans la documentation Apple sur la copie de collections , qui stipule:

Si vous avez besoin d'une véritable copie complète, par exemple lorsque vous avez un tableau de tableaux, vous pouvez archiver puis désarchiver la collection, à condition que le contenu soit conforme au protocole NSCoding. Un exemple de cette technique est présenté dans le Listing 3.

Listing 3 Une vraie copie profonde

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject:oldArray]];

Le hic, c'est que votre objet doit prendre en charge l'interface NSCoding, car elle sera utilisée pour stocker / charger les données.

Version Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Andrew Grant
la source
12
L'utilisation de NSArchiver et NSUnarchiver est une solution très lourde en termes de performances, si vos baies sont volumineuses. L'écriture d'une méthode de catégorie NSArray générique qui utilise le protocole NSCopying fera l'affaire, provoquant une simple «conservation» des objets immuables et une véritable «copie» des objets mutables.
Nikita Zhuk
Il est bon d'être prudent quant aux dépenses, mais NSCoding serait-il vraiment plus cher que le NSCopying utilisé dans la initWithArray:copyItems:méthode? Cette solution de contournement d'archivage / désarchivage semble très utile, compte tenu du nombre de classes de contrôle conformes à NSCoding mais pas à NSCopying.
Wienke
Je vous recommande vivement de ne jamais utiliser cette approche. La sérialisation n'est jamais plus rapide que la simple copie de mémoire.
Brett
1
Si vous avez des objets personnalisés, assurez-vous d'implémenter encodeWithCoder et initWithCoder afin de vous conformer au protocole NSCoding.
user523234
Évidemment, la discrétion du programmeur est fortement conseillée lors de l'utilisation de la sérialisation.
VH-NZZ
34

La copie par défaut donne une copie superficielle

En effet, l'appel copyest le même que copyWithZone:NULLcelui de la copie avec la zone par défaut. L' copyappel n'entraîne pas une copie complète. Dans la plupart des cas, cela vous donnerait une copie superficielle, mais dans tous les cas cela dépend de la classe. Pour une discussion approfondie, je recommande les rubriques de programmation des collections sur le site Apple Developer.

initWithArray: CopyItems: donne une copie profonde à un niveau

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:someArray copyItems:YES];

NSCoding est le moyen recommandé par Apple pour fournir une copie complète

Pour une véritable copie profonde (Array of Arrays), vous aurez besoin NSCodinget archiver / désarchiver l'objet:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
la source
1
C'est correct. Indépendamment de la popularité ou des cas de pointe optimisés prématurément sur la mémoire, les performances. En passant, ce piratage de ser-derser pour la copie profonde est utilisé dans de nombreux autres environnements linguistiques. À moins qu'il y ait une déduplication d'obj, cela garantit une bonne copie profonde complètement séparée de l'original.
1
Voici une URL de documentation Apple moins rottable developer.apple.com/library/mac/#documentation/cocoa/conceptual
7

Pour Dictonary

NSMutableDictionary *newCopyDict = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)objDict, kCFPropertyListMutableContainers);

Pour Array

NSMutableArray *myMutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL, arrData, kCFPropertyListMutableContainersAndLeaves);

Darshit Shah
la source
4

Non, il n'y a pas quelque chose de intégré dans les cadres pour cela. Les collections Cocoa prennent en charge les copies superficielles (avec les méthodes copyou arrayWithArray:) mais ne parlent même pas d'un concept de copie profonde.

Cela est dû au fait que la "copie complète" commence à devenir difficile à définir lorsque le contenu de vos collections commence à inclure vos propres objets personnalisés. La «copie profonde» signifie-t-elle que chaque objet du graphe d'objets est une référence unique par rapport à chaque objet du graphe d'objets d'origine?

S'il y avait un NSDeepCopyingprotocole hypothétique , vous pourriez le configurer et prendre des décisions dans tous vos objets, mais malheureusement il n'y en a pas. Si vous contrôliez la plupart des objets de votre graphique, vous pouvez créer vous-même ce protocole et l'implémenter, mais vous devrez ajouter une catégorie aux classes Foundation si nécessaire.

La réponse de @ AndrewGrant suggérant l'utilisation de l'archivage / désarchivage par clé est un moyen non performant mais correct et propre d'y parvenir pour des objets arbitraires. Ce livre va même jusqu'à présent, alors suggérez d' ajouter une catégorie à tous les objets qui fait exactement cela pour prendre en charge la copie profonde.

Ben Zotto
la source
2

J'ai une solution de contournement si j'essaie d'obtenir une copie complète pour les données compatibles JSON.

Il suffit NSDatad' NSArrayutiliser NSJSONSerializationet de recréer l'objet JSON, cela créera une nouvelle et nouvelle copie complète de NSArray/NSDictionaryavec de nouvelles références de mémoire.

Mais assurez-vous que les objets de NSArray / NSDictionary et leurs enfants doivent être sérialisables JSON.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
la source
1
Vous devez spécifier NSJSONReadingMutableContainersle cas d'utilisation dans cette question.
Nikolai Ruhe