Copia profunda de un NSArray

119

¿Hay alguna función incorporada que me permita realizar una copia profunda NSMutableArray?

Miré a mi alrededor, algunas personas dicen que [aMutableArray copyWithZone:nil]funciona como una copia profunda. Pero lo intenté y parece ser una copia superficial.

En este momento estoy haciendo la copia manualmente con un forbucle:

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

pero me gustaría una solución más limpia y concisa.

Ivan el Terrible
fuente
44
Las copias profundas y superficiales de @Genericrich son términos bastante bien definidos en el desarrollo de software. Google.com puede ayudar
Andrew Grant
1
tal vez parte de la confusión se deba a que el comportamiento de las -copycolecciones inmutables cambió entre Mac OS X 10.4 y 10.5: developer.apple.com/library/mac/releasenotes/Cocoa/… (desplácese hacia abajo hasta "Colecciones inmutables y comportamiento de copia")
user102008
1
@AndrewGrant Pensándolo bien, y con respeto, no estoy de acuerdo con que la copia profunda sea ​​un término bien definido. Dependiendo de la fuente que lea, no está claro si la recursividad ilimitada en estructuras de datos anidadas es un requisito de una operación de "copia profunda". En otras palabras, obtendrá respuestas contradictorias sobre si una operación de copia que crea un nuevo objeto cuyos miembros son copias superficiales de los miembros del objeto original es una operación de 'copia profunda' o no. Consulte stackoverflow.com/a/6183597/1709587 para obtener más información sobre esto (en un contexto de Java, pero de todos modos es relevante).
Mark Amery
@AndrewGrant Tengo que hacer una copia de seguridad de @MarkAmery y @Genericrich. Una copia profunda está bien definida si la clase raíz utilizada en una colección y todos sus elementos se pueden copiar. Este no es el caso de NSArray (y otras colecciones de objc). Si un elemento no se implementa copy, ¿qué se incluirá en la "copia profunda"? Si el elemento es otra colección, en copyrealidad no produce una copia (de la misma clase). Así que creo que es perfectamente válido discutir sobre el tipo de copia que se desea en el caso específico.
Nikolai Ruhe
@NikolaiRuhe Si un elemento no implementa NSCopying/ -copy, entonces no se puede copiar, por lo que nunca debe intentar hacer una copia, porque esa no es una capacidad para la que fue diseñado. En términos de la implementación de Cocoa, los objetos no copiables a menudo tienen algún estado de backend C al que están vinculados, por lo que piratear una copia directa del objeto podría generar condiciones de carrera o algo peor. Entonces, para responder “lo que se pondrá en la 'copia profunda'” - Se retuvo una ref. Lo único que puede poner en cualquier lugar cuando tiene un no NSCopyingobjeto.
Slipp D. Thompson

Respuestas:

210

Como dice explícitamente la documentación de Apple sobre copias profundas :

Si solo necesita una copia de un nivel de profundidad :

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

El código anterior crea una nueva matriz cuyos miembros son copias superficiales de los miembros de la matriz anterior.

Tenga en cuenta que si necesita copiar profundamente una estructura de datos anidada completa, lo que los documentos de Apple vinculados llaman una copia profunda verdadera , este enfoque no será suficiente. Consulte las otras respuestas aquí para eso.

François P.
fuente
Parece ser la respuesta correcta. La API indica que cada elemento recibe un mensaje [element copyWithZone:], que puede ser lo que estaba viendo. Si de hecho está viendo que enviar [NSMutableArray copyWithZone: nil] no hace una copia profunda, entonces una matriz de matrices puede que no se copie correctamente usando este método.
Ed Marty
7
No creo que esto funcione como se esperaba. De los documentos de Apple: "El método copyWithZone: realiza una copia superficial . Si tiene una colección de profundidad arbitraria, pasar SÍ para el parámetro de bandera realizará una copia inmutable del primer nivel debajo de la superficie. Si pasa NO la mutabilidad de el primer nivel no se ve afectado. En cualquier caso, la mutabilidad de todos los niveles más profundos no se ve afectada. "La pregunta SO se refiere a copias mutables profundas.
Joe D'Andrea
7
esto NO es una copia profunda
Daij-Djan
9
Esta es una respuesta incompleta. Esto da como resultado una copia profunda de un nivel. Si hay tipos más complejos dentro de la matriz, no proporcionará una copia profunda.
Cameron Lowell Palmer
7
Esto puede ser una copia profunda, dependiendo de cómo copyWithZone:se implemente en la clase receptora.
devios1
62

La única forma que conozco de hacer esto fácilmente es archivar y luego desarchivar inmediatamente su matriz. Se siente como un truco, pero en realidad se sugiere explícitamente en la Documentación de Apple sobre la copia de colecciones , que dice:

Si necesita una copia en profundidad verdadera, como cuando tiene una matriz de matrices, puede archivar y luego desarchivar la colección, siempre que todos los contenidos cumplan con el protocolo NSCoding. En el Listado 3 se muestra un ejemplo de esta técnica.

Listado 3 Una verdadera copia profunda

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

El problema es que su objeto debe ser compatible con la interfaz NSCoding, ya que se utilizará para almacenar / cargar los datos.

Versión Swift 2:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))
Andrew Grant
fuente
12
El uso de NSArchiver y NSUnarchiver es una solución muy pesada en cuanto al rendimiento, si sus matrices son grandes. Escribir un método de categoría NSArray genérico que use el protocolo NSCopying hará el truco, causando una simple 'retención' de objetos inmutables y una 'copia' real de los mutables.
Nikita Zhuk
Es bueno tener cuidado con los gastos, pero ¿NSCoding sería realmente más caro que el NSCopying utilizado en el initWithArray:copyItems:método? Esta solución de archivar / desarchivar parece muy útil, considerando cuántas clases de control se ajustan a NSCoding pero no a NSCopying.
Wienke
Recomiendo encarecidamente que nunca utilice este enfoque. La serialización nunca es más rápida que simplemente copiar la memoria.
Brett
1
Si tiene objetos personalizados, asegúrese de implementar encodeWithCoder e initWithCoder para cumplir con el protocolo NSCoding.
user523234
Obviamente, se recomienda encarecidamente la discreción del programador al utilizar la serialización.
VH-NZZ
34

Copiar por defecto da una copia superficial

Esto se debe a que llamar copyes lo mismo que copyWithZone:NULLtambién se conoce como copiar con la zona predeterminada. La copyllamada no da como resultado una copia profunda. En la mayoría de los casos le daría una copia superficial, pero en cualquier caso depende de la clase. Para una discusión exhaustiva, recomiendo los temas de programación de colecciones en el sitio para desarrolladores de Apple.

initWithArray: CopyItems: proporciona una copia profunda de un nivel

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

NSCoding es la forma recomendada por Apple de proporcionar una copia detallada

Para una verdadera copia profunda (Array of Arrays), necesitará NSCodingy archivar / desarchivar el objeto:

NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
Cameron Lowell Palmer
fuente
1
Esto es correcto. Independientemente de la popularidad o casos extremos optimizados prematuramente sobre la memoria, el rendimiento. Aparte, este truco de servidor para copia profunda se utiliza en muchos otros entornos de lenguaje. A menos que haya obj dedupe, esto garantiza una buena copia en profundidad completamente separada del original.
1
Aquí hay una URL de documento de Apple menos rotunda developer.apple.com/library/mac/#documentation/cocoa/conceptual/…
7

Para Dictonary

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

Para matriz

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

Darshit Shah
fuente
4

No, no hay nada integrado en los marcos para esto. Las colecciones de Cocoa admiten copias superficiales (con los métodos copyo arrayWithArray:), pero ni siquiera hablan de un concepto de copia profunda.

Esto se debe a que la "copia profunda" comienza a ser difícil de definir a medida que el contenido de sus colecciones comienza a incluir sus propios objetos personalizados. ¿"Copia profunda" significa que cada objeto en el gráfico de objetos es una referencia única relativa a cada objeto en el gráfico de objetos original?

Si hubiera algún NSDeepCopyingprotocolo hipotético , podría configurarlo y tomar decisiones en todos sus objetos, pero desafortunadamente no lo hay. Si controlaste la mayoría de los objetos en tu gráfico, podrías crear este protocolo tú mismo e implementarlo, pero necesitarías agregar una categoría a las clases Foundation según sea necesario.

La respuesta de @ AndrewGrant que sugiere el uso de archivado / desarchivado por clave es una forma no funcional pero correcta y limpia de lograr esto para objetos arbitrarios. Este libro incluso llega tan lejos, por lo que sugiere agregar una categoría a todos los objetos que haga exactamente eso para respaldar la copia profunda.

Ben Zotto
fuente
2

Tengo una solución alternativa si intento lograr una copia profunda para datos compatibles con JSON.

Simplemente toma NSDatade NSArrayusar NSJSONSerializationy volver a crear objetos JSON, esto creará una copia completa de nuevo y fresco NSArray/NSDictionarycon nuevas referencias a la memoria de ellos.

Pero asegúrese de que los objetos de NSArray / NSDictionary y sus hijos deben ser serializables en JSON.

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];
Mrug
fuente
1
Debe especificar NSJSONReadingMutableContainersel caso de uso en esta pregunta.
Nikolai Ruhe