Copiando em profundidade um NSArray

119

Existe alguma função incorporada que me permite copiar em profundidade um NSMutableArray?

Olhei em volta, algumas pessoas dizem que [aMutableArray copyWithZone:nil]funciona como cópia profunda. Mas tentei e parece ser uma cópia superficial.

No momento, estou fazendo a cópia manualmente com um forloop:

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

mas gostaria de uma solução mais limpa e sucinta.

Ivan, o Terrível
fonte
44
@Genericrich cópias profundas e superficiais são termos muito bem definidos no desenvolvimento de software. Google.com pode ajudar
Andrew Grant,
1
talvez parte da confusão seja porque o comportamento de -copycoleções imutáveis ​​mudou entre o Mac OS X 10.4 e 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (role para baixo até "Coleções imutáveis ​​e comportamento de cópia")
user102008
1
@AndrewGrant Pensando melhor, e com respeito, discordo que cópia profunda seja um termo bem definido. Dependendo da fonte que você lê, não está claro se a recursão ilimitada em estruturas de dados aninhadas é um requisito de uma operação de 'cópia profunda'. Em outras palavras, você obterá respostas conflitantes sobre se uma operação de cópia que cria um novo objeto cujos membros são cópias superficiais dos membros do objeto original é uma operação de 'cópia profunda' ou não. Consulte stackoverflow.com/a/6183597/1709587 para alguma discussão sobre isso (em um contexto Java, mas é relevante da mesma forma).
Mark Amery
@AndrewGrant, preciso fazer backup de @MarkAmery e @Genericrich. Uma cópia profunda é bem definida se a classe raiz usada em uma coleção e todos os seus elementos forem copiáveis. Este não é o caso com NSArray (e outras coleções objc). Se um elemento não implementa copy, o que deve ser colocado na "cópia profunda"? Se o elemento for outra coleção, copyna verdade não produz uma cópia (da mesma classe). Portanto, acho perfeitamente válido argumentar sobre o tipo de cópia desejada no caso específico.
Nikolai Ruhe
@NikolaiRuhe Se um elemento não implementar NSCopying/ -copy, então ele não pode ser copiado - então você nunca deve tentar fazer uma cópia dele, porque esse não é um recurso para o qual ele foi projetado. Em termos de implementação do Cocoa, objetos não copiáveis ​​geralmente têm algum estado de back-end C ao qual estão vinculados, portanto, hackear uma cópia direta do objeto pode levar a condições de corrida ou pior. Portanto, para responder “o que deve ser colocado na 'cópia profunda'” - Uma ref retida. A única coisa que você pode colocar em qualquer lugar quando você tem um não- NSCopyingobjeto.
Slipp D. Thompson

Respostas:

210

Como a documentação da Apple sobre cópias profundas afirma explicitamente:

Se você só precisa de uma cópia de um nível de profundidade :

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

O código acima cria uma nova matriz cujos membros são cópias superficiais dos membros da matriz antiga.

Observe que se você precisar copiar profundamente uma estrutura de dados aninhada inteira - o que os documentos vinculados da Apple chamam de cópia verdadeira e profunda - essa abordagem não será suficiente. Por favor, veja as outras respostas aqui para isso.

François P.
fonte
Parece ser a resposta correta. A API afirma que cada elemento recebe uma mensagem [element copyWithZone:], que pode ser o que você estava vendo. Se você está de fato vendo que o envio de [NSMutableArray copyWithZone: nil] não copia profundamente, então um array de arrays pode não ser copiado corretamente usando este método.
Ed Marty
7
Eu não acho que isso funcionará como esperado. Dos documentos da Apple: "O método copyWithZone: realiza uma cópia superficial . Se você tiver uma coleção de profundidade arbitrária, passar SIM para o parâmetro de sinalização executará uma cópia imutável do primeiro nível abaixo da superfície. Se você passar NÃO, a mutabilidade de o primeiro nível não é afetado. Em ambos os casos, a mutabilidade de todos os níveis mais profundos não é afetada. "A questão do SO diz respeito às cópias mutáveis ​​profundas.
Joe D'Andrea
7
esta NÃO é uma cópia profunda
Daij-Djan
9
Esta é uma resposta incompleta. Isso resulta em uma cópia profunda de um nível. Se houver tipos mais complexos no Array, ele não fornecerá uma cópia profunda.
Cameron Lowell Palmer
7
Esta pode ser uma cópia profunda, dependendo de como copyWithZone:é implementada na classe receptora.
devios1
62

A única maneira que conheço de fazer isso facilmente é arquivar e desarquivar imediatamente seu array. Parece um hack, mas na verdade é explicitamente sugerido na Documentação da Apple sobre cópias de coleções , que afirma:

Se você precisa de uma cópia verdadeira e profunda, como quando você tem uma matriz de matrizes, pode arquivar e desarquivar a coleção, desde que o conteúdo esteja em conformidade com o protocolo NSCoding. Um exemplo dessa técnica é mostrado na Listagem 3.

Listagem 3: uma cópia verdadeira e profunda

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

O problema é que seu objeto deve suportar a interface NSCoding, uma vez que ela será usada para armazenar / carregar os dados.

Versão Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Andrew Grant
fonte
12
Usar o NSArchiver e o NSUnarchiver é uma solução muito pesada em termos de desempenho, se seus arrays forem grandes. Escrever um método de categoria NSArray genérico que usa o protocolo NSCopying resolverá o problema, causando uma simples 'retenção' de objetos imutáveis ​​e uma 'cópia' real de objetos mutáveis.
Nikita Zhuk,
É bom ter cuidado com os gastos, mas o NSCoding seria realmente mais caro do que a cópia do NSC usada no initWithArray:copyItems:método? Esta solução alternativa de arquivamento / desarquivamento parece muito útil, considerando quantas classes de controle estão em conformidade com NSCoding, mas não com NSCopying.
Wienke
Eu recomendo fortemente que você nunca use essa abordagem. A serialização nunca é mais rápida do que apenas copiar a memória.
Brett
1
Se você tiver objetos personalizados, certifique-se de implementar encodeWithCoder e initWithCoder para estar em conformidade com o protocolo NSCoding.
user523234
Obviamente, a discrição do programador é fortemente recomendada ao usar a serialização.
VH-NZZ
34

Copiar por padrão fornece uma cópia superficial

Isso ocorre porque chamar copyé o mesmo que copyWithZone:NULLtambém conhecido como copiar com a zona padrão. A copychamada não resulta em uma cópia profunda. Na maioria dos casos, isso lhe daria uma cópia superficial, mas em qualquer caso, depende da classe. Para uma discussão completa, recomendo os Tópicos de programação de coleções no site do desenvolvedor da Apple.

initWithArray: CopyItems: fornece uma cópia profunda de um nível

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

NSCoding é a maneira recomendada pela Apple de fornecer uma cópia detalhada

Para uma cópia verdadeira em profundidade (Array of Arrays), você precisará NSCodingarquivar / desarquivar o objeto:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
fonte
1
Isto está certo. Independentemente da popularidade ou casos extremos prematuramente otimizados sobre memória, desempenho. Como um aparte, este hack do servidor para cópia profunda é usado em muitos outros ambientes de linguagem. A menos que haja obj dedupe, isso garante uma boa cópia profunda completamente separada do original.
1
Aqui está um url de documento menos rotativo da Apple developer.apple.com/library/mac/#documentation/cocoa/conceptual/…
7

Para Dictonário

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

Para Array

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

Darshit Shah
fonte
4

Não, não há algo embutido nas estruturas para isso. As coleções de cacau suportam cópias superficiais (com os métodos copyou arrayWithArray:), mas nem mesmo falam sobre o conceito de cópia profunda.

Isso ocorre porque a "cópia profunda" começa a se tornar difícil de definir conforme o conteúdo de suas coleções começa a incluir seus próprios objetos personalizados. "Cópia profunda" significa que cada objeto no gráfico de objeto é uma referência única em relação a cada objeto no gráfico de objeto original?

Se houvesse algum NSDeepCopyingprotocolo hipotético , você poderia configurá-lo e tomar decisões em todos os seus objetos, mas infelizmente não existe. Se você controlar a maioria dos objetos em seu gráfico, poderá criar esse protocolo sozinho e implementá-lo, mas precisará adicionar uma categoria às classes Foundation conforme necessário.

A resposta de @AndrewGrant sugerindo o uso de arquivamento / desarquivamento com chave é uma forma sem desempenho, mas correta e limpa de conseguir isso para objetos arbitrários. Este livro vai tão longe, então sugiro adicionar uma categoria a todos os objetos que faz exatamente isso para oferecer suporte a cópias profundas.

Ben Zotto
fonte
2

Eu tenho uma solução alternativa se tentar obter uma cópia profunda para dados compatíveis com JSON.

Basta ter NSDatade NSArrayusar NSJSONSerializatione, em seguida, recriar JSON objeto, isso vai criar uma cópia nova e fresca completa de NSArray/NSDictionarynovas referências de memória deles.

Mas certifique-se de que os objetos de NSArray / NSDictionary e seus filhos devem ser serializáveis ​​em JSON.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
fonte
1
Você deve especificar NSJSONReadingMutableContainerspara o caso de uso nesta questão.
Nikolai Ruhe