¿Cómo puedo duplicar o copiar un Objeto Administrado de Datos Básicos?

Tengo un objeto administrado (“A”) que contiene varios atributos y tipos de relaciones, y sus relaciones también tienen sus propios atributos y relaciones. Lo que me gustaría hacer es “copiar” o “duplicar” todo el gráfico objeto enraizado en el objeto “A”, y así crear un nuevo objeto “B” que sea muy similar a “A”.

Para ser más específico, ninguna de las relaciones contenidas por “B” (o sus hijos) debe apuntar a objetos relacionados con “A”. Debería haber un gráfico de objetos completamente nuevo con relaciones similares intactas, y todos los objetos con los mismos atributos, pero por supuesto diferentes id.

Existe la forma manual obvia de hacerlo, pero esperaba aprender de una manera más sencilla de hacerlo, lo que no era del todo evidente en la documentación de Core Data.

TIA!

Aquí hay una clase que creé para realizar una “copia profunda” de objetos administrados: atributos y relaciones. Tenga en cuenta que esto no comprueba contra los bucles en el gráfico de objetos. (Gracias Jaanus por el punto de partida …)

@interface ManagedObjectCloner : NSObject { } +(NSManagedObject *)clone:(NSManagedObject *)source inContext:(NSManagedObjectContext *)context; @end @implementation ManagedObjectCloner +(NSManagedObject *) clone:(NSManagedObject *)source inContext:(NSManagedObjectContext *)context{ NSString *entityName = [[source entity] name]; //create new object in data store NSManagedObject *cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; //loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[source valueForKey:attr] forKey:attr]; } //Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName]; for (NSRelationshipDescription *rel in relationships){ NSString *keyName = [NSString stringWithFormat:@"%@",rel]; //get a set of all objects in the relationship NSMutableSet *sourceSet = [source mutableSetValueForKey:keyName]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [ManagedObjectCloner clone:relatedObject inContext:context]; [clonedSet addObject:clonedRelatedObject]; } } return cloned; } @end 

Estas respuestas me acercaron mucho, aunque parecían tener algunas deficiencias:

Primero, tomé el consejo de ZS e hice una categoría en NSManagedObject, esto me pareció un poco más limpio.

2º, mi gráfico de objetos contiene relaciones de uno a uno, así que comencé con el ejemplo de levous, pero tenga en cuenta que el ejemplo de levous no es clonar el objeto en el caso de la relación de uno a uno. Esto causará un locking (intentando guardar un NSMO de un contexto en un contexto diferente). Lo he abordado en el siguiente ejemplo.

En tercer lugar, proporcioné un caché de objetos ya clonados, esto impide que los objetos se clonen dos veces y, por lo tanto, se dupliquen en el nuevo gráfico de objetos, y también evita los ciclos.

Cuarto, he agregado una lista negra (lista de tipos de entidades que no deben clonarse). Lo hice en parte para resolver una deficiencia de mi solución final, que describiré a continuación.

NOTA: si usa lo que entiendo para ser una mejor práctica de CoreData, siempre proporcionando relaciones inversas, entonces esto probablemente clonará todos los objetos que tengan una relación con el objeto que desea clonar. Si usa inversos y tiene un único objeto raíz que conoce todos los demás objetos, es probable que lo clone todo. Mi solución a esto fue agregar la lista negra y pasar el tipo de Entidad que sabía que era el padre de uno de los objetos que quería clonar. Esto parece funcionar para mí. 🙂

Feliz clonación!

 // NSManagedObject+Clone.h #import  @interface NSManagedObject (Clone) - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude; @end // NSManagedObject+Clone.m #import "NSManagedObject+Clone.h" @implementation NSManagedObject (Clone) - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSArray *)namesOfEntitiesToExclude { NSString *entityName = [[self entity] name]; if ([namesOfEntitiesToExclude containsObject:entityName]) { return nil; } NSManagedObject *cloned = [alreadyCopied objectForKey:[self objectID]]; if (cloned != nil) { return cloned; } //create new object in data store cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; [alreadyCopied setObject:cloned forKey:[self objectID]]; //loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[self valueForKey:attr] forKey:attr]; } //Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName]; for (NSString *relName in [relationships allKeys]){ NSRelationshipDescription *rel = [relationships objectForKey:relName]; NSString *keyName = rel.name; if ([rel isToMany]) { //get a set of all objects in the relationship NSMutableSet *sourceSet = [self mutableSetValueForKey:keyName]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude]; [clonedSet addObject:clonedRelatedObject]; } }else { NSManagedObject *relatedObject = [self valueForKey:keyName]; if (relatedObject != nil) { NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude]; [cloned setValue:clonedRelatedObject forKey:keyName]; } } } return cloned; } - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude { return [self cloneInContext:context withCopiedCache:[NSMutableDictionary dictionary] exludeEntities:namesOfEntitiesToExclude]; } @end 

He actualizado la respuesta de user353759 para apoyar las relaciones OneOne.

 @interface ManagedObjectCloner : NSObject { } +(NSManagedObject *)clone:(NSManagedObject *)source inContext:(NSManagedObjectContext *)context; @end @implementation ManagedObjectCloner +(NSManagedObject *) clone:(NSManagedObject *)source inContext:(NSManagedObjectContext *)context{ NSString *entityName = [[source entity] name]; //create new object in data store NSManagedObject *cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; //loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[source valueForKey:attr] forKey:attr]; } //Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName]; for (NSString *relName in [relationships allKeys]){ NSRelationshipDescription *rel = [relationships objectForKey:relName]; NSString *keyName = [NSString stringWithFormat:@"%@",rel]; if ([rel isToMany]) { //get a set of all objects in the relationship NSMutableSet *sourceSet = [source mutableSetValueForKey:keyName]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [ManagedObjectCloner clone:relatedObject inContext:context]; [clonedSet addObject:clonedRelatedObject]; } }else { [cloned setValue:[source valueForKey:keyName] forKey:keyName]; } } return cloned; } 

Esta es la respuesta de @Derricks, modificada para admitir las nuevas relaciones de iOS 6.0 pedidas a muchas personas al interrogar la relación para ver si está ordenada. Mientras estuve allí, agregué un método de clonación más simple para el caso común de clonación dentro del mismo NSManagedObjectContext.

 // // NSManagedObject+Clone.h // Tone Poet // // Created by Mason Kramer on 5/31/13. // Copyright (c) 2013 Mason Kramer. The contents of this file are available for use by anyone, for any purpose whatsoever. // #import  #import  @interface NSManagedObject (Clone) { } -(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSArray *)namesOfEntitiesToExclude; -(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude; -(NSManagedObject *) clone; @end // // NSManagedObject+Clone.m // Tone Poet // // Created by Mason Kramer on 5/31/13. // Copyright (c) 2013 Mason Kramer. The contents of this file are available for use by anyone, for any purpose whatsoever. // #import "NSManagedObject+Clone.h" @implementation NSManagedObject (Clone) -(NSManagedObject *) clone { return [self cloneInContext:[self managedObjectContext] exludeEntities:@[]]; } - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSArray *)namesOfEntitiesToExclude { NSString *entityName = [[self entity] name]; if ([namesOfEntitiesToExclude containsObject:entityName]) { return nil; } NSManagedObject *cloned = [alreadyCopied objectForKey:[self objectID]]; if (cloned != nil) { return cloned; } //create new object in data store cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; [alreadyCopied setObject:cloned forKey:[self objectID]]; //loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[self valueForKey:attr] forKey:attr]; } //Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName]; for (NSString *relName in [relationships allKeys]){ NSRelationshipDescription *rel = [relationships objectForKey:relName]; NSString *keyName = rel.name; if ([rel isToMany]) { if ([rel isOrdered]) { NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName]; NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude]; [clonedSet addObject:clonedRelatedObject]; [clonedSet addObject:clonedRelatedObject]; } } else { NSMutableSet *sourceSet = [self mutableSetValueForKey:keyName]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude]; [clonedSet addObject:clonedRelatedObject]; } } } else { NSManagedObject *relatedObject = [self valueForKey:keyName]; if (relatedObject != nil) { NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude]; [cloned setValue:clonedRelatedObject forKey:keyName]; } } } return cloned; } -(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSArray *)namesOfEntitiesToExclude { return [self cloneInContext:context withCopiedCache:[NSMutableDictionary dictionary] exludeEntities:namesOfEntitiesToExclude]; } @end 

He notado un par de errores con las respuestas actuales. En primer lugar, algo parece estar mutando el conjunto de muchos objetos relacionados a medida que se repiten. En segundo lugar, no estoy seguro de si algo ha cambiado dentro de la API, pero el uso de la representación String de NSRelationshipDescription como clave arrojaba excepciones al tomar esos objetos relacionados.

Hice algunos ajustes, hice algunas pruebas básicas y parece funcionar. Si alguien quiere investigar más, ¡sería genial!

 @implementation NSManagedObjectContext (DeepCopy) -(NSManagedObject *) clone:(NSManagedObject *)source{ NSString *entityName = [[source entity] name]; //create new object in data store NSManagedObject *cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self]; //loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:self] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[source valueForKey:attr] forKey:attr]; } //Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:self] relationshipsByName]; for (NSString *relName in [relationships allKeys]){ NSRelationshipDescription *rel = [relationships objectForKey:relName]; if ([rel isToMany]) { //get a set of all objects in the relationship NSArray *sourceArray = [[source mutableSetValueForKey:relName] allObjects]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:relName]; for(NSManagedObject *relatedObject in sourceArray) { NSManagedObject *clonedRelatedObject = [self clone:relatedObject]; [clonedSet addObject:clonedRelatedObject]; } } else { [cloned setValue:[source valueForKey:relName] forKey:relName]; } } return cloned; } @end 

He modificado la respuesta de Derrick , que funcionó perfectamente para mí, para admitir las relaciones ordenadas disponibles en iOS 5.0 y Mac OS X 10.7:

 // // NSManagedObject+Clone.h // #import  #ifndef CD_CUSTOM_DEBUG_LOG #define CD_CUSTOM_DEBUG_LOG NSLog #endif @interface NSManagedObject (Clone) - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context excludeEntities:(NSArray *)namesOfEntitiesToExclude; @end // // NSManagedObject+Clone.m // #import "NSManagedObject+Clone.h" @interface NSManagedObject (ClonePrivate) - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary **)alreadyCopied excludeEntities:(NSArray *)namesOfEntitiesToExclude; @end @implementation NSManagedObject (Clone) - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary **)alreadyCopied excludeEntities:(NSArray *)namesOfEntitiesToExclude { if (!context) { CD_CUSTOM_DEBUG_LOG(@"%@:%@ Try to clone NSManagedObject in the 'nil' context.", THIS_CLASS, THIS_METHOD); return nil; } NSString *entityName = [[self entity] name]; if ([namesOfEntitiesToExclude containsObject:entityName]) { return nil; } NSManagedObject *cloned = nil; if (alreadyCopied != NULL) { cloned = [*alreadyCopied objectForKey:[self objectID]]; if (cloned) { return cloned; } // Create new object in data store cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; [*alreadyCopied setObject:cloned forKey:[self objectID]]; } else { CD_CUSTOM_DEBUG_LOG(@"%@:%@ NULL pointer was passed in 'alreadyCopied' argument.", THIS_CLASS, THIS_METHOD); } // Loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[self valueForKey:attr] forKey:attr]; } // Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName]; NSArray *relationshipKeys = [relationships allKeys]; for (NSString *relName in relationshipKeys) { NSRelationshipDescription *rel = [relationships objectForKey:relName]; NSString *keyName = [rel name]; if ([rel isToMany]) { if ([rel isOrdered]) { // Get a set of all objects in the relationship NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName]; NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName]; for (id relatedObject in sourceSet) { //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied excludeEntities:namesOfEntitiesToExclude]; if (clonedRelatedObject) { [clonedSet addObject:clonedRelatedObject]; } } } else { // Get a set of all objects in the relationship NSMutableSet *sourceSet = [self mutableSetValueForKey:keyName]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; for (id relatedObject in sourceSet) { //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied excludeEntities:namesOfEntitiesToExclude]; if (clonedRelatedObject) { [clonedSet addObject:clonedRelatedObject]; } } } } else { NSManagedObject *relatedObject = [self valueForKey:keyName]; if (relatedObject) { NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied excludeEntities:namesOfEntitiesToExclude]; [cloned setValue:clonedRelatedObject forKey:keyName]; } } } return cloned; } - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context excludeEntities:(NSArray *)namesOfEntitiesToExclude { NSMutableDictionary* mutableDictionary = [NSMutableDictionary dictionary]; return [self cloneInContext:context withCopiedCache:&mutableDictionary excludeEntities:namesOfEntitiesToExclude]; } @end 

Tenía una necesidad real de solucionar el problema de la copia masiva que @derrick reconoció en su respuesta original. Modifiqué de la versión de MasonK. Esto no tiene mucha elegancia en las versiones anteriores; pero parece resolver un problema clave (duplicados involuntarios de entidades similares) en mi aplicación.

 // // NSManagedObject+Clone.h #import  #import  @interface NSManagedObject (Clone) { } -(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude isFirstPass:(BOOL)firstPass; -(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude; -(NSManagedObject *) clone; @end // // NSManagedObject+Clone.m // #import "NSManagedObject+Clone.h" @implementation NSManagedObject (Clone) -(NSManagedObject *) clone { NSMutableArray *emptyArray = [NSMutableArray arrayWithCapacity:1]; return [self cloneInContext:[self managedObjectContext] exludeEntities:emptyArray]; } - (NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context withCopiedCache:(NSMutableDictionary *)alreadyCopied exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude isFirstPass:(BOOL)firstPass { NSString *entityName = [[self entity] name]; if ([namesOfEntitiesToExclude containsObject:entityName]) { return nil; } NSManagedObject *cloned = [alreadyCopied objectForKey:[self objectID]]; if (cloned != nil) { return cloned; } //create new object in data store cloned = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; [alreadyCopied setObject:cloned forKey:[self objectID]]; //loop through all attributes and assign then to the clone NSDictionary *attributes = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[self valueForKey:attr] forKey:attr]; } //Inverse relationships can cause all of the entities under one area to get duplicated //This is the reason for "isFirstPass" and "excludeEntities" if (firstPass == TRUE) { [namesOfEntitiesToExclude addObject:entityName]; firstPass=FALSE; } //Loop through all relationships, and clone them. NSDictionary *relationships = [[NSEntityDescription entityForName:entityName inManagedObjectContext:context] relationshipsByName]; for (NSString *relName in [relationships allKeys]){ NSRelationshipDescription *rel = [relationships objectForKey:relName]; NSString *keyName = rel.name; if ([rel isToMany]) { if ([rel isOrdered]) { NSMutableOrderedSet *sourceSet = [self mutableOrderedSetValueForKey:keyName]; NSMutableOrderedSet *clonedSet = [cloned mutableOrderedSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude isFirstPass:firstPass]; if (clonedRelatedObject != nil) { [clonedSet addObject:clonedRelatedObject]; [clonedSet addObject:clonedRelatedObject]; } } } else { NSMutableSet *sourceSet = [self mutableSetValueForKey:keyName]; NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName]; NSEnumerator *e = [sourceSet objectEnumerator]; NSManagedObject *relatedObject; while ( relatedObject = [e nextObject]){ //Clone it, and add clone to set NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude isFirstPass:firstPass]; if (clonedRelatedObject != nil) { [clonedSet addObject:clonedRelatedObject]; } } } } else { NSManagedObject *relatedObject = [self valueForKey:keyName]; if (relatedObject != nil) { NSManagedObject *clonedRelatedObject = [relatedObject cloneInContext:context withCopiedCache:alreadyCopied exludeEntities:namesOfEntitiesToExclude isFirstPass:firstPass]; if (clonedRelatedObject != nil) { [cloned setValue:clonedRelatedObject forKey:keyName]; } } } } return cloned; } -(NSManagedObject *)cloneInContext:(NSManagedObjectContext *)context exludeEntities:(NSMutableArray *)namesOfEntitiesToExclude { return [self cloneInContext:context withCopiedCache:[NSMutableDictionary dictionary] exludeEntities:namesOfEntitiesToExclude isFirstPass:TRUE]; } @end 

Lo que está pidiendo se llama una “copia profunda”. Debido a que puede ser muy costoso (como en el uso de memoria ilimitada) y muy difícil de corregir (considere los bucles en el gráfico de objetos), Core Data no le ofrece esta función.

Sin embargo, a menudo existe una architecture que evita la necesidad. En lugar de hacer una copia de un gráfico de objetos completo, quizás pueda crear una nueva entidad que encapsule las diferencias (o diferencias futuras) que tendría si copiara el gráfico de objetos y luego solo hace referencia al gráfico original. En otras palabras, cree una instancia de una nueva entidad “personalizable” y no copie todo el gráfico de objetos. Por ejemplo, considere un conjunto de casas en hilera. Cada uno tiene marcos y electrodomésticos idénticos, pero el propietario puede personalizar la pintura y los muebles. En lugar de copiar profundamente el gráfico completo de la casa para cada propietario, tenga una entidad de “pintura y mobiliario”, que haga referencia al propietario y al modelo de la casa, para cada propietario.

La mejor categoría de COPIA PROFUNDA con el mantenimiento de las relaciones es https://gist.github.com/advantis/7642084

¿Algo como esto? (no probado) Esta sería la “forma manual” que mencionas, pero estaría sincronizada automáticamente con los cambios del modelo y tal que no tendrías que ingresar manualmente todos los nombres de los atributos.

Swift 3:

 extension NSManagedObject { func shallowCopy() -> NSManagedObject? { guard let context = managedObjectContext, let entityName = entity.name else { return nil } let copy = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) let attributes = entity.attributesByName for (attrKey, _) in attributes { copy.setValue(value(forKey: attrKey), forKey: attrKey) } return copy } } 

C objective:

 @interface MyObject (Clone) - (MyObject *)clone; @end @implementation MyObject (Clone) - (MyObject *)clone{ MyObject *cloned = [NSEntityDescription insertNewObjectForEntityForName:@"MyObject" inManagedObjectContext:moc]; NSDictionary *attributes = [[NSEntityDescription entityForName:@"MyObject" inManagedObjectContext:moc] attributesByName]; for (NSString *attr in attributes) { [cloned setValue:[self valueForKey:attr] forKey:attr]; } return cloned; } @end 

Esto te devolverá un clon con todos los atributos y sin relaciones copiadas.

Esto se llama una “copia profunda”. Debido a que puede ser sorprendentemente costoso, muchos idiomas / bibliotecas no lo admiten de inmediato y requieren que usted haga lo propio. Cocoa es lamentablemente uno de ellos.

Aquí tienes mi enfoque rápido 3 :

 func shallowCopy(copyRelations: Bool) -> NSManagedObject? { guard let context = managedObjectContext, let entityName = entity.name else { return nil } let copy = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) let attributes = entity.attributesByName for (attrKey, _) in attributes { copy.setValue(value(forKey: attrKey), forKey: attrKey) } if copyRelations { let relations = entity.relationshipsByName for (relKey, relValue) in relations { if relValue.isToMany { let sourceSet = mutableSetValue(forKey: relKey) let clonedSet = copy.mutableSetValue(forKey: relKey) let enumerator = sourceSet.objectEnumerator() while let relatedObject = enumerator.nextObject() { let clonedRelatedObject = (relatedObject as! NSManagedObject).shallowCopy(copyRelations: false) clonedSet.add(clonedRelatedObject!) } } else { copy.setValue(value(forKey: relKey), forKey: relKey) } } } return copy } 

También:

 [clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[properties allKeys]]]; [clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[attributes allKeys]]]; 

If you want to only relate Entities in the Relationship Hierarchie you only have to add the following Code to the solution of Dmitry

Between this

 NSString *entityName = [[self entity] name]; 

HERE if ([namesOfEntitiesToExclude containsObject:entityName]) {

 NSMutableArray *arrayToOnlyRelate = [NSMutableArray arrayWithObjects:@"ENTITY 1",@"ENTITY 2",@"ENTITY 3", nil]; if ([arrayToOnlyRelate containsObject:entityName]) { return self; } 

My take on this is at https://gist.github.com/jpmhouston/7958fceae9216f69178d4719a3492577

  • passes rel.inverseRelationship.name into the recursive method to omit visiting the inverse relationships rather than maintaining a set of alreadyCopied objects

  • shallow or deep copies

  • accepts keypaths of relationships to not clone, but to either omit, or to simply copy if the inverse is a to-many relationship

  • workaround for ordered,to-many relationships ending up in backwards order – simply iterate over the source entities backwards 🙂 i’m not sure if this a good idea or if it even works all the time

Feedback & comments welcome, especially if someone can elaborate on Benjohn ‘s comment on mis-ordering above ” The work around for this is to build the complete ordered set and then assign using the primitive KVO variant. ” and can improve my ordered,to-many workaround.

Also, I’m using MagicalRecord and so my code assumes that, including providing easy methods that use its default context.

Swift 4.0 version

 import UIKit import CoreData class ManagedObjectCloner: NSObject { static func cloneObject(source :NSManagedObject, context :NSManagedObjectContext) -> NSManagedObject{ let entityName = source.entity.name let cloned = NSEntityDescription.insertNewObject(forEntityName: entityName!, into: context) let attributes = NSEntityDescription.entity(forEntityName: entityName!, in: context)?.attributesByName for (key,_) in attributes! { cloned.setValue(source.value(forKey: key), forKey: key) } let relationships = NSEntityDescription.entity(forEntityName: entityName!, in: context)?.relationshipsByName for (key,_) in relationships! { let sourceSet = source.mutableSetValue(forKey: key) let clonedSet = cloned.mutableSetValue(forKey: key) let e = sourceSet.objectEnumerator() var relatedObj = e.nextObject() as? NSManagedObject while ((relatedObj) != nil) { let clonedRelatedObject = ManagedObjectCloner.cloneObject(source: relatedObj!, context: context) clonedSet.add(clonedRelatedObject) relatedObj = e.nextObject() as? NSManagedObject } } return cloned } }