Copia profonda di un NSArray


119

Esiste una funzione incorporata che mi consenta di copiare in profondità un NSMutableArray?

Mi sono guardato intorno, alcune persone dicono che [aMutableArray copyWithZone:nil]funziona come copia profonda. Ma ho provato e sembra essere una copia superficiale.

In questo momento sto facendo manualmente la copia con un forciclo:

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

ma vorrei una soluzione più pulita e concisa.


44
Le copie profonde e superficiali di @Genericrich sono termini abbastanza ben definiti nello sviluppo del software. Google.com può aiutare
Andrew Grant

1
forse un po 'di confusione è dovuta al fatto che il comportamento delle -copyraccolte immutabili è cambiato tra Mac OS X 10.4 e 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (scorri verso il basso fino a "Collezioni immutabili e comportamento di copia")
user102008

1
@AndrewGrant Ripensandoci e con rispetto, non sono d'accordo sul fatto che la copia profonda sia un termine ben definito. A seconda della fonte che leggi, non è chiaro se la ricorsione illimitata in strutture di dati annidate sia un requisito per un'operazione di "copia profonda". In altre parole, otterrai risposte contrastanti sul fatto che un'operazione di copia che crea un nuovo oggetto i cui membri sono copie superficiali dei membri dell'oggetto originale sia o meno un'operazione di "copia profonda". Vedi stackoverflow.com/a/6183597/1709587 per qualche discussione su questo (in un contesto Java, ma è rilevante lo stesso).
Mark Amery

@AndrewGrant Devo eseguire il backup di @MarkAmery e @Genericrich. Una copia completa è ben definita se la classe radice utilizzata in una raccolta e tutti i suoi elementi sono copiabili. Questo non è il caso di NSArray (e altre raccolte objc). Se un elemento non viene implementato copy, cosa deve essere inserito nella "copia completa"? Se l'elemento è un'altra raccolta, in copyrealtà non produce una copia (della stessa classe). Quindi penso che sia perfettamente valido discutere sul tipo di copia desiderata nel caso specifico.
Nikolai Ruhe

@NikolaiRuhe Se un elemento non implementa NSCopying/ -copy, non è copiabile, quindi non dovresti mai provare a farne una copia, perché non è una capacità per cui è stato progettato. In termini di implementazione di Cocoa, gli oggetti non copiabili hanno spesso uno stato backend C a cui sono legati, quindi l'hacking di una copia diretta dell'oggetto potrebbe portare a condizioni di gara o peggio. Quindi, per rispondere "cosa deve essere inserito nella 'copia completa'" - A ref. L'unica cosa che puoi mettere ovunque quando hai un non NSCopyingoggetto.
Slipp D. Thompson

Risposte:


210

Come la documentazione Apple sulle copie profonde afferma esplicitamente:

Se hai bisogno solo di una copia approfondita di un livello :

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

Il codice precedente crea un nuovo array i cui membri sono copie superficiali dei membri del vecchio array.

Si noti che se è necessario copiare in profondità un'intera struttura dati nidificata, ciò che i documenti Apple collegati chiamano una vera copia profonda , questo approccio non sarà sufficiente. Si prega di vedere le altre risposte qui per questo.


Sembra essere la risposta corretta. L'API afferma che ogni elemento riceve un messaggio [element copyWithZone:], che potrebbe essere quello che stavi vedendo. Se in effetti stai vedendo che l'invio di [NSMutableArray copyWithZone: nil] non esegue la copia completa, allora un array di array potrebbe non essere copiato correttamente utilizzando questo metodo.
Ed Marty

7
Non credo che funzionerà come previsto. Dai documenti di Apple: "Il metodo copyWithZone: esegue una copia superficiale . Se si dispone di una raccolta di profondità arbitraria, passando YES per il parametro flag verrà eseguita una copia immutabile del primo livello sotto la superficie. Se si supera NO la mutabilità di il primo livello è inalterato. In entrambi i casi, la mutabilità di tutti i livelli più profondi rimane inalterata. "La questione SO riguarda le copie mutabili profonde.
Joe D'Andrea

7
questa NON è una copia profonda
Daij-Djan

9
Questa è una risposta incompleta. Ciò si traduce in una copia approfondita di un livello. Se sono presenti tipi più complessi all'interno dell'array, non fornirà una copia completa.
Cameron Lowell Palmer

7
Questa può essere una copia completa, a seconda di come copyWithZone:viene implementata nella classe ricevente.
devios1

62

L'unico modo che conosco per farlo facilmente è archiviare e quindi annullare immediatamente l'archiviazione del tuo array. Sembra un po 'un trucco, ma in realtà è esplicitamente suggerito nella documentazione di Apple sulla copia delle raccolte , che afferma:

Se è necessaria una vera copia completa, ad esempio quando si dispone di un array di array, è possibile archiviare e quindi annullare l'archiviazione della raccolta, a condizione che i contenuti siano tutti conformi al protocollo NSCoding. Un esempio di questa tecnica è mostrato nel Listato 3.

Listato 3 Una vera copia completa

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

Il problema è che il tuo oggetto deve supportare l'interfaccia NSCoding, poiché questa verrà utilizzata per memorizzare / caricare i dati.

Versione Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))

12
L'utilizzo di NSArchiver e NSUnarchiver è una soluzione molto impegnativa dal punto di vista delle prestazioni, se gli array sono di grandi dimensioni. La scrittura di un metodo di categoria NSArray generico che utilizza il protocollo NSCopying farà il trucco, causando un semplice "mantenimento" di oggetti immutabili e una vera "copia" di quelli modificabili.
Nikita Zhuk

È bene essere cauti riguardo alla spesa, ma NSCoding sarebbe davvero più costoso dell'NSCopying utilizzato nel initWithArray:copyItems:metodo? Questa soluzione di archiviazione / disarchiviazione sembra molto utile, considerando quante classi di controllo sono conformi a NSCoding ma non a NSCopying.
Wienke

Consiglio vivamente di non utilizzare mai questo approccio. La serializzazione non è mai più veloce della semplice copia della memoria.
Brett

1
Se disponi di oggetti personalizzati, assicurati di implementare encodeWithCoder e initWithCoder in modo da rispettare il protocollo NSCoding.
user523234

Ovviamente la discrezione del programmatore è fortemente consigliata quando si utilizza la serializzazione.
VH-NZZ

34

Copia per impostazione predefinita fornisce una copia superficiale

Questo perché la chiamata copyè la stessa cosa copyWithZone:NULLnota anche come copia con la zona predefinita. La copychiamata non si traduce in una copia completa. Nella maggior parte dei casi ti darebbe una copia superficiale, ma in ogni caso dipende dalla classe. Per una discussione approfondita, consiglio gli argomenti di programmazione delle raccolte sul sito degli sviluppatori Apple.

initWithArray: CopyItems: fornisce una copia profonda di un livello

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

NSCoding è il modo consigliato da Apple per fornire una copia completa

Per una vera copia completa (Array of Arrays) è necessario NSCodingarchiviare / rimuovere dall'archivio l'oggetto:

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

1
Questo è corretto. Indipendentemente dalla popolarità o dai casi limite ottimizzati prematuramente su memoria, prestazioni. Per inciso, questo hack ser-derser per la copia profonda viene utilizzato in molti altri ambienti linguistici. A meno che non ci sia obj deduplica, questo garantisce una buona copia completa completamente separata dall'originale.

1
Ecco un URL documento Apple meno ruotabile developer.apple.com/library/mac/#documentation/cocoa/conceptual/…

7

Per Dictonary

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

Per Array

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


4

No, non c'è qualcosa integrato nei framework per questo. Le collezioni di cacao supportano copie superficiali (con i metodi copyo arrayWithArray:) ma non parlano nemmeno di un concetto di copia profonda.

Questo perché la "copia approfondita" inizia a diventare difficile da definire quando i contenuti delle tue raccolte iniziano a includere i tuoi oggetti personalizzati. "Copia approfondita" significa che ogni oggetto nel grafo degli oggetti è un riferimento univoco relativo a ogni oggetto nel grafo degli oggetti originale?

Se ci fosse un NSDeepCopyingprotocollo ipotetico , potresti impostarlo e prendere decisioni in tutti i tuoi oggetti, ma sfortunatamente non c'è. Se hai controllato la maggior parte degli oggetti nel tuo grafico, potresti creare questo protocollo da solo e implementarlo, ma dovresti aggiungere una categoria alle classi Foundation, se necessario.

La risposta di @AndrewGrant suggerisce che l'uso dell'archiviazione / disarchiviazione con chiave è un modo non efficiente ma corretto e pulito per ottenere questo risultato per oggetti arbitrari. Questo libro va anche così lontano, quindi suggeriamo di aggiungere una categoria a tutti gli oggetti che fa esattamente questo per supportare la copia profonda.


2

Ho una soluzione alternativa se provo a ottenere una copia completa per i dati compatibili con JSON.

Basta prendere NSDatadi NSArrayutilizzare NSJSONSerializatione quindi ricreare JSON oggetto, questo creerà una copia completa di nuovo e fresco NSArray/NSDictionary, con nuove referenze loro ricordo.

Ma assicurati che gli oggetti di NSArray / NSDictionary e dei loro figli debbano essere serializzabili in JSON.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];

1
Devi specificare NSJSONReadingMutableContainersper il caso d'uso in questa domanda.
Nikolai Ruhe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.