Głębokie kopiowanie NSArray

119

Czy jest jakaś wbudowana funkcja, która pozwala mi głęboko skopiować plik NSMutableArray?

Rozejrzałem się, niektórzy mówią, że [aMutableArray copyWithZone:nil]działa jak głęboka kopia. Ale próbowałem i wydaje się, że jest to płytka kopia.

W tej chwili ręcznie wykonuję kopię z forpętlą:

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

ale chciałbym uzyskać czystsze, bardziej zwięzłe rozwiązanie.

ivanTheTerrible
źródło
44
@Genericrich głębokie i płytkie kopie są dość dobrze zdefiniowanymi terminami w rozwoju oprogramowania. Google.com może pomóc
Andrew Grant
1
być może część zamieszania wynika z tego, że zachowanie -copyniezmiennych kolekcji zmieniło się między Mac OS X 10.4 i 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/ ... (przewiń w dół do „Niezmienne kolekcje i zachowanie kopiowania”)
user102008
1
@AndrewGrant Po dalszych myślach iz szacunkiem nie zgadzam się, że głęboka kopia jest dobrze zdefiniowanym terminem. W zależności od czytanego źródła nie jest jasne, czy nieograniczona rekursja do zagnieżdżonych struktur danych jest wymogiem operacji „głębokiego kopiowania”. Innymi słowy, otrzymasz sprzeczne odpowiedzi dotyczące tego, czy operacja kopiowania, która tworzy nowy obiekt, którego elementy członkowskie są płytkimi kopiami elementów składowych oryginalnego obiektu, jest operacją „głębokiej kopii”, czy nie. Zobacz stackoverflow.com/a/6183597/1709587, aby zapoznać się z niektórymi dyskusjami na ten temat (w kontekście Java, ale jest to jednak istotne).
Mark Amery,
@AndrewGrant Muszę wykonać kopię zapasową @MarkAmery i @Genericrich. Głęboka kopia jest dobrze zdefiniowana, jeśli klasa główna używana w kolekcji i wszystkie jej elementy można skopiować. Nie dotyczy to NSArray (i innych kolekcji objc). Jeśli element się nie implementuje copy, co należy umieścić w „głębokiej kopii”? Jeśli element jest inną kolekcją, w copyrzeczywistości nie daje kopii (tej samej klasy). Dlatego uważam, że spór o rodzaj kopii, jaki ma być w konkretnym przypadku, jest całkowicie zasadny.
Nikolai Ruhe
@NikolaiRuhe Jeśli element nie implementuje NSCopying/ -copy, to nie można go skopiować - więc nigdy nie powinieneś próbować tworzyć jego kopii, ponieważ nie jest to zdolność, do której został zaprojektowany. Jeśli chodzi o implementację Cocoa, obiekty nie do skopiowania często mają stan zaplecza C, z którym są powiązane, więc hakowanie bezpośredniej kopii obiektu może prowadzić do warunków wyścigu lub gorzej. Tak więc odpowiadając „co należy umieścić w„ głębokiej kopii ”” - zachowany ref. Jedyną rzeczą, którą możesz umieścić w dowolnym miejscu, gdy masz NSCopyingobiekt niebędący przedmiotem.
Slipp D. Thompson,

Odpowiedzi:

210

Jak wyraźnie stwierdza dokumentacja Apple dotycząca głębokich kopii :

Jeśli potrzebujesz tylko kopii jednopoziomowej :

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

Powyższy kod tworzy nową tablicę, której elementy członkowskie są płytkimi kopiami elementów starej tablicy.

Zwróć uwagę, że jeśli chcesz głęboko skopiować całą zagnieżdżoną strukturę danych - to, co połączone dokumenty Apple nazywają prawdziwą głęboką kopią - to podejście nie wystarczy. Zobacz inne odpowiedzi tutaj.

François P.
źródło
Wydaje się, że to poprawna odpowiedź. API stwierdza, że ​​każdy element otrzymuje komunikat [element copyWithZone:], który może być tym, co właśnie widzisz. Jeśli w rzeczywistości widzisz, że wysłanie [NSMutableArray copyWithZone: nil] nie kopiuje głęboko, to tablica tablic może nie kopiować poprawnie przy użyciu tej metody.
Ed Marty
7
Myślę, że to nie zadziała zgodnie z oczekiwaniami. Z dokumentów Apple: „Metoda copyWithZone: wykonuje płytką kopię . Jeśli masz kolekcję o dowolnej głębokości, przekazanie TAK dla parametru flag spowoduje wykonanie niezmiennej kopii pierwszego poziomu pod powierzchnią. Jeśli zdasz NIE zmienność pierwszy poziom pozostaje niezmieniony. W obu przypadkach zmienność wszystkich głębszych poziomów pozostaje niezmieniona. ”Pytanie SO dotyczy głęboko mutowalnych kopii.
Joe D'Andrea
7
to NIE jest głęboka kopia
Daij-Djan
9
To jest niepełna odpowiedź. Powoduje to jednopoziomową głęboką kopię. Jeśli w tablicy są bardziej złożone typy, nie zapewni to dokładnej kopii.
Cameron Lowell Palmer
7
Może to być głęboka kopia, w zależności od tego, jak copyWithZone:jest zaimplementowana w klasie odbierającej.
devios1
62

Jedyny sposób, w jaki mogę to łatwo zrobić, to zarchiwizować, a następnie natychmiast cofnąć archiwizację tablicy. Wydaje się, że to trochę włamania, ale w rzeczywistości jest wyraźnie sugerowane w dokumentacji Apple dotyczącej kopiowania kolekcji , która stwierdza:

Jeśli potrzebujesz prawdziwej, głębokiej kopii, na przykład gdy masz tablicę tablic, możesz zarchiwizować, a następnie cofnąć archiwizację kolekcji, pod warunkiem, że cała zawartość jest zgodna z protokołem NSCoding. Przykład tej techniki przedstawiono na Listingu 3.

Listing 3 Prawdziwie głęboka kopia

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

Problem polega na tym, że obiekt musi obsługiwać interfejs NSCoding, ponieważ będzie on używany do przechowywania / ładowania danych.

Wersja Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Andrew Grant
źródło
12
Używanie NSArchiver i NSUnarchiver jest bardzo wydajnym rozwiązaniem, jeśli masz duże macierze. Napisanie ogólnej metody kategorii NSArray, która używa protokołu NSCopying, załatwi sprawę, powodując proste „zachowanie” niezmiennych obiektów i prawdziwą „kopię” obiektów zmiennych.
Nikita Zhuk
Dobrze jest być ostrożnym, jeśli chodzi o koszty, ale czy NSCoding naprawdę będzie droższe niż NSCopying użyte w initWithArray:copyItems:metodzie? To obejście archiwizacji / przywracania archiwizacji wydaje się bardzo przydatne, biorąc pod uwagę, ile klas kontrolnych jest zgodnych z NSCoding, ale nie z NSCopying.
Wienke
Gorąco polecam, abyś nigdy nie stosował tego podejścia. Serializacja nigdy nie jest szybsza niż zwykłe kopiowanie pamięci.
Brett
1
Jeśli masz obiekty niestandardowe, pamiętaj o zaimplementowaniu encodeWithCoder i initWithCoder, aby zachować zgodność z protokołem NSCoding.
user523234
Oczywiście podczas korzystania z serializacji zdecydowanie zaleca się dyskrecję programisty.
VH-NZZ
34

Kopiuj domyślnie daje płytką kopię

Dzieje się tak, ponieważ dzwonienie copyjest tym samym, co copyWithZone:NULLnazywamy kopiowaniem ze strefą domyślną. copyWywołanie nie powoduje głębokiej kopii. W większości przypadków dałoby to płytką kopię, ale w każdym razie zależy to od klasy. Aby uzyskać dokładną dyskusję, polecam tematy dotyczące programowania kolekcji w witrynie Apple Developer.

initWithArray: CopyItems: daje jednopoziomową głęboką kopię

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

NSCoding jest zalecanym przez Apple sposobem dostarczania głębokiej kopii

Aby uzyskać prawdziwie głęboką kopię (Array of Arrays), będziesz potrzebować NSCodingi zarchiwizować / cofnąć archiwizację obiektu:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
źródło
1
To jest poprawne. Niezależnie od popularności lub przedwcześnie zoptymalizowanych przypadków skrajnych dotyczących pamięci, wydajności. Na marginesie, ten hack serwera do głębokiego kopiowania jest używany w wielu innych środowiskach językowych. O ile nie ma wyraźnej dedupe, gwarantuje to dobrą, głęboką kopię całkowicie oddzielną od oryginału.
1
Oto mniej podatny na zgniatanie adresu URL dokumentu Apple developer.apple.com/library/mac/#documentation/cocoa/conceptual/…
7

Dla Dictonary

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

Dla Array

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

Darshit Shah
źródło
4

Nie, nie ma czegoś wbudowanego w ramy tego. Kolekcje Cocoa obsługują płytkie kopie ( metodami copylub arrayWithArray:), ale nawet nie wspominają o koncepcji głębokiego kopiowania.

Dzieje się tak, ponieważ „głęboka kopia” zaczyna być trudna do zdefiniowania, gdy zawartość kolekcji zaczyna zawierać własne obiekty niestandardowe. Czy „głęboka kopia” oznacza, że każdy obiekt w grafie obiektów jest unikalnym odniesieniem do każdego obiektu w oryginalnym grafie obiektów?

Gdyby istniał jakiś hipotetyczny NSDeepCopyingprotokół, mógłbyś go ustawić i podejmować decyzje we wszystkich swoich obiektach, ale niestety tak nie jest. Jeśli kontrolujesz większość obiektów na swoim wykresie, możesz sam stworzyć ten protokół i zaimplementować go, ale w razie potrzeby musisz dodać kategorię do klas Foundation.

Odpowiedź @AndrewGranta sugerująca użycie archiwizacji / przywracania z archiwum z kluczem jest nieefektywnym, ale poprawnym i czystym sposobem osiągnięcia tego dla dowolnych obiektów. Ta książka posuwa się nawet tak daleko, że sugeruje dodanie kategorii do wszystkich obiektów, która robi dokładnie to, aby wspierać głębokie kopiowanie.

Ben Zotto
źródło
2

Mam obejście, jeśli próbuję uzyskać głęboką kopię danych zgodnych z formatem JSON.

Wystarczy wziąć NSDataz NSArrayużyciemNSJSONSerialization , a następnie odtworzyć obiekt JSON, to stworzy zupełnie nową i świeżą kopię NSArray/NSDictionaryz nowych referencji pamięć o nich.

Ale upewnij się, że obiekty NSArray / NSDictionary i ich elementy podrzędne muszą być serializowane w formacie JSON.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
źródło
1
Musisz określić NSJSONReadingMutableContainersdla przypadku użycia w tym pytaniu.
Nikolai Ruhe