Tiefes Kopieren eines NSArray

119

Gibt es eine eingebaute Funktion, mit der ich eine tief kopieren kann NSMutableArray?

Ich habe mich umgesehen, manche Leute sagen, es [aMutableArray copyWithZone:nil]funktioniert als tiefe Kopie. Aber ich habe es versucht und es scheint eine flache Kopie zu sein.

Im Moment mache ich das Kopieren manuell mit einer forSchleife:

//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];
            }
        }
    }
}

aber ich hätte gerne eine sauberere, prägnantere Lösung.

Ivan der Schreckliche
quelle
44
@Genericrich tiefe und flache Kopien sind ziemlich gut definierte Begriffe in der Softwareentwicklung. Google.com kann helfen
Andrew Grant
1
Möglicherweise liegt ein Teil der Verwirrung darin, dass sich das Verhalten von -copyunveränderlichen Sammlungen zwischen Mac OS X 10.4 und 10.5 geändert hat: developer.apple.com/library/mac/releasenotes/Cocoa/… (scrollen Sie nach unten zu "Unveränderliche Sammlungen und Kopierverhalten")
user102008
1
@ AndrewGrant Nach weiteren Überlegungen und mit Respekt bin ich nicht der Meinung, dass Deep Copy ein klar definierter Begriff ist. Abhängig davon, welche Quelle Sie lesen, ist unklar, ob eine unbegrenzte Rekursion in verschachtelte Datenstrukturen eine Voraussetzung für eine Deep-Copy-Operation ist. Mit anderen Worten, Sie erhalten widersprüchliche Antworten darauf, ob eine Kopieroperation, die ein neues Objekt erstellt, dessen Mitglieder flache Kopien der Mitglieder des ursprünglichen Objekts sind, eine "Tiefenkopie" -Operation ist oder nicht. Weitere Informationen hierzu finden Sie unter stackoverflow.com/a/6183597/1709587 (in einem Java-Kontext, der jedoch relevant ist).
Mark Amery
@AndrewGrant Ich muss @MarkAmery und @Genericrich sichern. Eine tiefe Kopie ist gut definiert, wenn die in einer Sammlung verwendete Stammklasse und alle ihre Elemente kopierbar sind. Dies ist bei NSArray (und anderen Objektsammlungen) nicht der Fall. Wenn ein Element nicht implementiert wird copy, was soll in die "tiefe Kopie" eingefügt werden? Wenn das Element eine andere Sammlung ist, copywird tatsächlich keine Kopie (derselben Klasse) ausgegeben. Ich denke, es ist absolut richtig, über die Art der Kopie zu streiten, die im konkreten Fall gewünscht wird.
Nikolai Ruhe
@NikolaiRuhe Wenn ein Element NSCopying/ nicht implementiert -copy, kann es nicht kopiert werden. Sie sollten daher niemals versuchen, eine Kopie davon zu erstellen , da dies keine Funktion ist, für die es entwickelt wurde. In Bezug auf die Implementierung von Cocoa haben nicht kopierbare Objekte häufig einen C-Backend-Status, an den sie gebunden sind. Das Hacken einer direkten Kopie des Objekts kann daher zu Rennbedingungen oder schlechteren Bedingungen führen. Um zu antworten: "Was soll in die" tiefe Kopie "gelegt werden ?" Das einzige, was Sie irgendwo platzieren können, wenn Sie ein Nicht- NSCopyingObjekt haben.
Slipp D. Thompson

Antworten:

210

In der Apple-Dokumentation zu Deep Copies heißt es ausdrücklich:

Wenn Sie nur eine einstufige Kopie benötigen :

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

Der obige Code erstellt ein neues Array, dessen Mitglieder flache Kopien der Mitglieder des alten Arrays sind.

Beachten Sie, dass dieser Ansatz nicht ausreicht , wenn Sie eine gesamte verschachtelte Datenstruktur tief kopieren müssen - was in den verknüpften Apple-Dokumenten als echte tiefe Kopie bezeichnet wird. Bitte beachten Sie die anderen Antworten hier.

François P.
quelle
Scheint die richtige Antwort zu sein. Die API gibt an, dass jedes Element eine [element copyWithZone:] - Nachricht erhält, die möglicherweise angezeigt wird. Wenn Sie tatsächlich feststellen, dass das Senden von [NSMutableArray copyWithZone: nil] nicht tief kopiert, kopiert ein Array von Arrays mit dieser Methode möglicherweise nicht richtig.
Ed Marty
7
Ich denke nicht, dass dies wie erwartet funktionieren wird. Aus den Apple-Dokumenten: "Die copyWithZone: -Methode führt eine flache Kopie durch . Wenn Sie eine Sammlung beliebiger Tiefe haben, führt das Übergeben von YES für den Flag-Parameter eine unveränderliche Kopie der ersten Ebene unter der Oberfläche aus. Wenn Sie NO übergeben, ist die Veränderbarkeit von Die erste Ebene bleibt davon unberührt. In beiden Fällen bleibt die Veränderlichkeit aller tieferen Ebenen unberührt. "Die SO-Frage betrifft tief veränderbare Kopien.
Joe D'Andrea
7
Dies ist keine tiefe Kopie
Daij-Djan
9
Dies ist eine unvollständige Antwort. Dies führt zu einer einstufigen tiefen Kopie. Wenn das Array komplexere Typen enthält, wird keine tiefe Kopie bereitgestellt.
Cameron Lowell Palmer
7
Dies kann eine tiefe Kopie sein, abhängig davon, wie copyWithZone:sie in der empfangenden Klasse implementiert ist.
Devios1
62

Die einzige Möglichkeit, die ich kenne, besteht darin, Ihr Array zu archivieren und dann sofort wieder zu archivieren. Es fühlt sich ein bisschen wie ein Hack, aber tatsächlich explizit in der vorgeschlagenen Apple - Dokumentation über das Kopieren von Sammlungen , die besagt:

Wenn Sie eine echte Deep Copy benötigen, z. B. wenn Sie über ein Array von Arrays verfügen, können Sie die Sammlung archivieren und anschließend die Archivierung aufheben, sofern alle Inhalte dem NSCoding-Protokoll entsprechen. Ein Beispiel für diese Technik ist in Listing 3 dargestellt.

Listing 3: Eine echte, tiefe Kopie

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

Der Haken ist, dass Ihr Objekt die NSCoding-Schnittstelle unterstützen muss, da diese zum Speichern / Laden der Daten verwendet wird.

Swift 2 Version:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Andrew Grant
quelle
12
Die Verwendung von NSArchiver und NSUnarchiver ist in Bezug auf die Leistung eine sehr schwere Lösung, wenn Ihre Arrays groß sind. Das Schreiben einer generischen NSArray-Kategoriemethode, die das NSCopying-Protokoll verwendet, reicht aus, um unveränderliche Objekte einfach beizubehalten und veränderbare Objekte zu kopieren.
Nikita Zhuk
Es ist gut, vorsichtig mit den Kosten umzugehen, aber wäre NSCoding wirklich teurer als das in der initWithArray:copyItems:Methode verwendete NSCopying ? Diese Problemumgehung für das Archivieren / Aufheben der Archivierung scheint sehr nützlich zu sein, wenn man bedenkt, wie viele Steuerungsklassen der NSCoding, aber nicht der NSCopying entsprechen.
Wienke
Ich würde Ihnen wärmstens empfehlen, diesen Ansatz niemals zu verwenden. Die Serialisierung ist niemals schneller als nur das Kopieren von Speicher.
Brett
1
Wenn Sie benutzerdefinierte Objekte haben, müssen Sie encodeWithCoder und initWithCoder implementieren, um das NSCoding-Protokoll einzuhalten.
user523234
Offensichtlich wird die Diskretion des Programmierers bei der Verwendung der Serialisierung dringend empfohlen.
VH-NZZ
34

Standardmäßig kopieren ergibt eine flache Kopie

Dies liegt daran, dass das Aufrufen copydasselbe ist wie copyWithZone:NULLdas Kopieren mit der Standardzone. Der copyAnruf führt nicht zu einer tiefen Kopie. In den meisten Fällen erhalten Sie eine flache Kopie, die jedoch in jedem Fall von der Klasse abhängt. Für eine gründliche Diskussion empfehle ich die Sammlungsprogrammierungsthemen auf der Apple Developer-Website.

initWithArray: CopyItems: Gibt eine einstufige tiefe Kopie

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

NSCoding ist die von Apple empfohlene Methode, um eine tiefe Kopie bereitzustellen

Für eine echte Deep Copy (Array of Arrays) benötigen NSCodingSie das Objekt und archivieren / entarchivieren es:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
quelle
1
Das ist richtig. Unabhängig von der Popularität oder vorzeitig optimierten Randfällen in Bezug auf Speicher und Leistung. Abgesehen davon wird dieser Server-Hack für Deep Copy in vielen anderen Sprachumgebungen verwendet. Sofern nicht obj dedupe vorhanden ist, garantiert dies eine gute, tiefe Kopie, die vollständig vom Original getrennt ist.
1
Hier ist eine weniger verrottbare Apple-Dokument-URL developer.apple.com/library/mac/#documentation/cocoa/conceptual/…
7

Für Diktonar

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

Für Array

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

Darshit Shah
quelle
4

Nein, dafür ist nichts in die Frameworks eingebaut. Kakaosammlungen unterstützen flache Kopien (mit dem copyoder demarrayWithArray: Methoden), sprechen jedoch nicht einmal über ein Deep-Copy-Konzept.

Dies liegt daran, dass die Definition von "Deep Copy" immer schwieriger wird, wenn der Inhalt Ihrer Sammlungen einschließlich Ihrer eigenen benutzerdefinierten Objekte beginnt. Bedeutet "Deep Copy", dass jedes Objekt im Objektdiagramm eine eindeutige Referenz in Bezug auf jedes Objekt im ursprünglichen Objektdiagramm ist?

Wenn es eine hypothetische gab NSDeepCopying Protokoll , könnten Sie dieses einrichten und Entscheidungen in all Ihren Objekten treffen, aber leider nicht. Wenn Sie die meisten Objekte in Ihrem Diagramm gesteuert haben, können Sie dieses Protokoll selbst erstellen und implementieren, müssen jedoch den Foundation-Klassen nach Bedarf eine Kategorie hinzufügen.

Die Antwort von @ AndrewGrant, die die Verwendung von Archivierung / Archivierung ohne Schlüssel vorschlägt, ist eine nicht leistungsfähige, aber korrekte und saubere Methode, um dies für beliebige Objekte zu erreichen. Dieses Buch geht sogar so weit, also schlagen Sie vor , allen Objekten eine Kategorie hinzuzufügen, die genau das tut, um das Tiefenkopieren zu unterstützen.

Ben Zotto
quelle
2

Ich habe eine Problemumgehung, wenn ich versuche, eine tiefe Kopie für JSON-kompatible Daten zu erhalten.

Nehmen Sie einfach NSDatadie NSArrayVerwendung NSJSONSerializationund dann JSON - Objekt erstellen, das wird eine ganz neue und frische Kopie erstellen NSArray/NSDictionarymit neuen Speicherreferenzen von ihnen.

Stellen Sie jedoch sicher, dass die Objekte von NSArray / NSDictionary und ihre untergeordneten Objekte JSON-serialisierbar sind.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
quelle
1
Sie müssen NSJSONReadingMutableContainersfür den Anwendungsfall in dieser Frage angeben .
Nikolai Ruhe