Как я могу продублировать или скопировать управляемый объект Core Data?

У меня есть управляемый объект («A»), который содержит различные атрибуты и типы отношений, и его отношения также имеют свои собственные атрибуты и отношения. Что я хотел бы сделать, так это «скопировать» или «продублировать» весь граф объекта, основанный на объекте «A», и таким образом создать новый объект «B», очень похожий на «A».

Чтобы быть более конкретным, ни одно из отношений, содержащихся в «B» (или его дочерних элементах), не должно указывать на объекты, связанные с «A». Должен быть совершенно новый граф объектов с похожими отношениями без изменений, и все объекты имеют одинаковые атрибуты, но, конечно, разные идентификаторы.

Есть очевидный ручной способ сделать это, но я надеялся узнать о более простых способах сделать это, что не было полностью очевидно из документации Core Data.

TIA!


person Mani    schedule 28.04.2010    source источник
comment
Это круто, и все такое, но теперь здесь 13 разных форков одного и того же алгоритма, каждая со своими интересными функциями и исправлениями! … ТАК. возможно, следует предоставить репозиторий git для каждого вопроса, чтобы решить эту проблему :-)   -  person Benjohn    schedule 13.03.2015
comment
Я думаю, что нет очевидного ручного способа сделать это, поскольку NSManagedObject является частью произвольно сложного графа отношений. Вам нужно будет обращаться к круговым отношениям, обратным отношениям и встречаться с одними и теми же объектами снова и снова. Этот вопрос соответствует вопросу о копировании подграфов. Мой опыт работы со многими моделями CoreData любой сложности показывает, что вам обычно нужен Shallow cloner, который будет клонировать объект и его атрибуты, а также клонировать его отношения. не связанные сущности. Значение - оригинал будет относиться к тем же сущностям, что и клон.   -  person Motti Shneor    schedule 16.01.2018


Ответы (17)


Вот класс, который я создал для выполнения «глубокой копии» управляемых объектов: атрибутов и отношений. Обратите внимание, что это не проверяет циклы в графе объектов. (Спасибо, Яанус за отправную точку ...)

@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
person user353759    schedule 29.05.2010
comment
Это потрясающе! Моя единственная загвоздка: UITableView не всегда правильно анимирует (фактически новую) ячейку. Интересно, касается ли это insertNewObjectForEntityForName: inManagedObjectContext:, ЗАТЕМ выполнение глубокого копирования, которое может вызвать больше сообщений NSFetchedResultsControllerDelegate (?). У меня нет циклов, и данные, которые я копирую, сами по себе не очень глубокие, так что, надеюсь, это хорошо. Возможно, у меня есть способ каким-то образом создать весь клонированный объект и ЗАТЕМ вставить его одним махом или хотя бы отложить уведомление о том, что добавление произошло. Смотрим, возможно ли это. - person Joe D'Andrea; 28.07.2010
comment
это фантастика! Однако одно замечание. Строка "for (NSRelationshipDescription * rel in Relations)" неверна. отношения - это NSDictionary. Я изменил, чтобы получить NSRelationshipDescription 'для (NSString * relName в [взаимосвязи allKeys]) {NSRelationshipDescription * rel = [взаимосвязи objectForKey: relName]; 'и проверить isToMany - person levous; 15.03.2011
comment
Я получаю эту ошибку. Есть указатели? *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x7003f80> was mutated while being enumerated.<CFBasicHash 0x7003f80 [0x1314400]> - person Mustafa; 23.05.2011
comment
Примечание: у меня есть многоуровневый сложный график с отношениями «один к одному» и «один ко многим». Я применил исправление, предложенное levous для индивидуальных отношений, и, похоже, оно работает, но в целом рутина не работает. - person Mustafa; 23.05.2011
comment
@Mustafa Я знаю, что это было несколько месяцев назад, но у меня была такая же проблема с мутированным набором. Проверьте мой ответ, чтобы узнать о возможном решении. - person Nathan Gaskin; 10.11.2011
comment
NSString * keyName = [NSString stringWithFormat: @% @, rel] не содержит имени отношения в iOS6, используйте [NSString stringWithFormat: @% @, rel.name]. В противном случае keyName содержит всю строку описания отношения - person Christoph; 26.02.2013
comment
это сработало! но когда я меняю источник на nil, клон тоже меняется! как такое возможно?!?! - person Aviram Netanel; 04.04.2016

Эти ответы меня очень близко подобрали, хотя, похоже, в них есть некоторые недостатки:

Во-первых, я последовал совету Z S и сделал его категорией на NSManagedObject, мне это показалось немного чище.

Во-вторых, мой граф объектов содержит отношения «к одному», поэтому я начал с примера levous, но обратите внимание, что пример levous не клонирует объект в случае отношения «к одному». Это вызовет сбой (попытка сохранить NSMO из одного контекста в другом контексте). Я обратился к этому в приведенном ниже примере.

В-третьих, я предоставил кеш уже клонированных объектов, это предотвращает двойное клонирование объектов и, следовательно, дублирование в новом графе объектов, а также предотвращает циклы.

В-четвертых, я добавил черный список (список типов сущностей, которые нельзя клонировать). Я сделал это частично, чтобы решить один недостаток моего окончательного решения, о котором я расскажу ниже.

ПРИМЕЧАНИЕ: если вы используете то, что я понимаю, как лучшую практику CoreData, всегда обеспечивая обратные отношения, то это, скорее всего, клонирует все объекты, которые имеют отношение к объекту, который вы хотите клонировать. Если вы используете инверсии и у вас есть единственный корневой объект, который знает обо всех других объектах, то вы, скорее всего, клонируете все это целиком. Моим решением было добавить черный список и передать тип Entity, который, как я знал, был родительским для одного из объектов, которые я хотел клонировать. Похоже, это работает для меня. :)

Удачного клонирования!

// NSManagedObject+Clone.h
#import <CoreData/CoreData.h>

@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
person Derrick    schedule 30.09.2011
comment
Это сработало для меня как прелесть. У меня были отношения как со многими, так и с одним. У меня были объекты в иерархии, которые я не хотел клонировать. Мои тесты показывают, что он работал нормально. Я должен тебе пива. К вашему сведению, я преобразовал обратно в метод класса из категории по причинам архитектуры приложения. Я также заменил copiedCache аргумент на excludeEntities, чтобы следовать рекомендациям Apple по именованию, когда имена методов постоянно удлиняются. И исправили ошибку в написании исключения. - person Jeff; 05.03.2013
comment
Этот код работает в iOS6 с ARC для вложенных отношений и с использованием RestKit! Спасибо - person Alex Stone; 05.04.2013
comment
Это тот, который был ближе всего к работе. Однако некоторые из моих отношений определены как упорядоченные, что является новостью для iOS 6. Итак, я публикую свою модификацию, которая это проверяет. - person masonk; 31.05.2013
comment
Здесь это наиболее полная реализация. - person Diego Barros; 22.07.2013
comment
Это лучшее решение, но есть одна ошибка. Вам необходимо проверить, является ли clonedRelatedObject нулевым, прежде чем добавлять его в clonedSet. Если связанный объект был частью namesOfEntitiesToExclude, он будет равен нулю и выдает ошибку при попытке добавить его в clonedSet. - person Joe; 19.10.2013
comment
Я пробовал это, но получаю эту ошибку в этой строке. NSMutableSet * clonedSet = [клонированный mutableSetValueForKey: keyName]; *** Завершение работы приложения из-за неперехваченного исключения «NSInvalidArgumentException», причина: «NSManagedObjects сущности« Форма »не поддерживают -mutableSetValueForKey: для свойства« содержит » - person Mark Bridges; 15.11.2013
comment
@MarkBridges Я знаю, что это поздно, но в любом случае я получил эту ошибку, потому что у меня было упорядоченное отношение, поэтому я использовал решение Дмитрия Макаренко, где упорядоченное отношение также обрабатывается, и оно сработало. - person anoop4real; 08.03.2016

Я обновил ответ user353759 для поддержки отношений toOne.

@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;
}
person levous    schedule 15.03.2011
comment
Одно небольшое улучшение может заключаться в том, чтобы сделать его категорией в NSManagedObject, так что вам не нужно передавать контекст и исходный объект. - person Z S; 28.07.2011
comment
Итак, если у меня есть ObjectA и ObjectB, я могу скопировать все записи в ObjectA и вставить их в ObjectB? Затем удалить ObjectA и оставить только ObjectB со всеми моими записями? Это цель глубокой копии? Потому что это то, что я ищу. - person RyeMAC3; 01.10.2012
comment
Не работает для моего проекта RestKit в iOS6 с ARC. Я получаю EXC_BAD_ACCESS в NSEnumerator * e = [sourceSet objectEnumerator]; - person Alex Stone; 05.04.2013

Это ответ @Derricks, измененный для поддержки отношений упорядоченных ко многим в новой версии iOS 6.0 путем опроса отношения, чтобы узнать, упорядочено ли оно. Пока я был там, я добавил более простой метод -clone для общего случая клонирования в том же 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 <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@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
person masonk    schedule 31.05.2013
comment
Произошел сбой при попытке использовать ответ Деррика, но этот работает для меня. - person Mark Bridges; 15.11.2013
comment
Это отличный ответ, его нужно продвигать! - person Aqib Mumtaz; 06.01.2015
comment
Обычно это работает, за исключением того, что в моем случае порядок упорядоченных отношений меняется на обратный каждый раз, когда я копирую! Любые идеи? Кроме того, есть ли причина, по которой вы делаете [clonedSet addObject:clonedRelatedObject]; дважды для упорядоченных отношений? - person elsurudo; 04.08.2015
comment
Я думаю, что порядок здесь не работает. Кажется, есть ошибка, которую я исправил в собственном варианте. Для упорядоченных отношений, которые имеют обратное, клонирование сначала в глубину (как используется во всех этих реализациях) может рекурсивно вызывать установку другого конца отношения, которое обновляет упорядоченный набор по мере его построения. Чтобы решить эту проблему, необходимо создать полный упорядоченный набор, а затем назначить его с помощью варианта primitive KVO. - person Benjohn; 18.12.2015
comment
@elsurudo: Думаю, это опечатка. - person koen; 16.04.2016
comment
Тогда плохая опечатка;) - person Roberto Frontado; 26.04.2017

Swift 5

Это основано на вкладе @Derrick & @Dmitry Makarenko & @ masonk, объединив все вместе, улучшив и превратив в решение, подходящее для 2020 года.

  • Обрабатывает как отношения 1 к 1, так и 1 ко многим
  • Обрабатывает упорядоченные отношения
  • Копирует весь граф NSManagedObject (используя уже скопированный кеш)
  • Реализовано как расширение NSManagedObject

.

import CoreData

extension NSManagedObject {

    func copyEntireObjectGraph(context: NSManagedObjectContext) -> NSManagedObject {

        var cache = Dictionary<NSManagedObjectID, NSManagedObject>()
        return cloneObject(context: context, cache: &cache)

    }

    func cloneObject(context: NSManagedObjectContext, cache alreadyCopied: inout Dictionary<NSManagedObjectID, NSManagedObject>) -> NSManagedObject {

        guard let entityName = self.entity.name else {
            fatalError("source.entity.name == nil")
        }

        if let storedCopy = alreadyCopied[self.objectID] {
            return storedCopy
        }

        let cloned = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
    alreadyCopied[self.objectID] = cloned

        if let attributes = NSEntityDescription.entity(forEntityName: entityName, in: context)?.attributesByName {

            for key in attributes.keys {
                cloned.setValue(self.value(forKey: key), forKey: key)
            }

        }

        if let relationships = NSEntityDescription.entity(forEntityName: entityName, in: context)?.relationshipsByName {

            for (key, value) in relationships {

                if value.isToMany {

                    if let sourceSet = self.value(forKey: key) as? NSMutableOrderedSet {

                        guard let clonedSet = cloned.value(forKey: key) as? NSMutableOrderedSet else {
                            fatalError("Could not cast relationship \(key) to an NSMutableOrderedSet")
                        }

                        let enumerator = sourceSet.objectEnumerator()

                        var nextObject = enumerator.nextObject() as? NSManagedObject

                        while let relatedObject = nextObject {

                            let clonedRelatedObject = relatedObject.cloneObject(context: context, cache: &alreadyCopied)
                            clonedSet.add(clonedRelatedObject)
                            nextObject = enumerator.nextObject() as? NSManagedObject

                        }

                    } else if let sourceSet = self.value(forKey: key) as? NSMutableSet {

                        guard let clonedSet = cloned.value(forKey: key) as? NSMutableSet else {
                            fatalError("Could not cast relationship \(key) to an NSMutableSet")
                        }

                        let enumerator = sourceSet.objectEnumerator()

                        var nextObject = enumerator.nextObject() as? NSManagedObject

                        while let relatedObject = nextObject {

                            let clonedRelatedObject = relatedObject.cloneObject(context: context, cache: &alreadyCopied)
                            clonedSet.add(clonedRelatedObject)
                            nextObject = enumerator.nextObject() as? NSManagedObject

                        }

                    }

                } else {

                    if let relatedObject = self.value(forKey: key) as? NSManagedObject {

                        let clonedRelatedObject = relatedObject.cloneObject(context: context, cache: &alreadyCopied)
                        cloned.setValue(clonedRelatedObject, forKey: key)

                    }

                }

            }

        }

        return cloned

    }

}

Использование:

let myManagedObjectCopy = myManagedObject.copyEntireObjectGraph(context: myContext)
person Geoff H    schedule 23.12.2019
comment
Не могли бы вы кое-что прояснить - этот клон автоматически добавляется в базу данных или находится в подвешенном состоянии до тех пор, пока не будет сохранен контекст? - person Rillieux; 20.12.2020

Я заметил пару ошибок с текущими ответами. Во-первых, кажется, что что-то изменяет набор связанных объектов to-Many по мере его повторения. Во-вторых, я не уверен, что что-то изменилось в API, но использование строкового представления NSRelationshipDescription в качестве ключа вызывало исключения при захвате этих связанных объектов.

Я сделал несколько настроек, провел базовое тестирование, и, похоже, он работает. Если кто-то захочет продолжить расследование, это будет здорово!

@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
person Nathan Gaskin    schedule 10.11.2011
comment
[cloned setValue:[source valueForKey:relName] forKey:relName]; не клонирует целевой объект в отношении к одному и приводит к исключению, потому что вы можете попытаться связать объекты из разных контекстов. Это также несовместимо с веткой toMany. Вероятно, это опечатка / небольшая оплошность. - person Felix Lamouroux; 12.11.2012
comment
Здесь есть много разных ответов, но этот работал с объектами, у которых были атрибуты, которые были как одиночными, так и множественными. - person MGM; 09.03.2014

Я изменил ответ Деррика, который отлично сработал для меня, для поддержки упорядоченные отношения, доступные в iOS. 5.0 и Mac OS X 10.7:

//
//  NSManagedObject+Clone.h
//

#import <CoreData/CoreData.h>

#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
person Dmitry Makarenko    schedule 03.03.2012
comment
Похоже, что на самом деле это не правильно сохраняет порядок отношений. Я дам вам знать, если найду исправление. Кажется, что порядок заканчивается в обратном порядке, но это может быть неопределенным. - person Duane Fields; 02.07.2012
comment
Уверены ли вы? Это сработало для меня, и я не могу найти проблему во фрагменте кода, который я поставил в качестве ответа. - person Dmitry Makarenko; 04.07.2012
comment
Да, порядок неопределенный. У меня есть страница с упорядоченным соотношением штрихов. Когда я клонирую страницу, я получаю все отношения штрихов, но они не в порядке. - person Duane Fields; 05.07.2012
comment
У меня всегда получается обратный заказ. Вы случайно не нашли исправление? - person elsurudo; 04.08.2015

Мне действительно нужно было обойти проблему массового копирования, которую @derrick признал в своем первоначальном ответе. Я модифицировал версию MasonK. В нем нет такой элегантности, которая была в предыдущих версиях; но похоже, что это решает ключевую проблему (непреднамеренные дубликаты похожих объектов) в моем приложении.

//
//  NSManagedObject+Clone.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@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
person JustinThibault    schedule 24.07.2013
comment
Да, исключение сущностей не работало с версией MasonK! Спасибо Джастин - person Damien Romito; 11.09.2015
comment
Обратите внимание - где-то у вас дублируется строка кода: [clonedSet addObject: clonedRelatedObject]; Это может не иметь эффекта, так как clonedSet является NSSet - все же это нежелательно. - person Motti Shneor; 16.01.2018

Что-то вроде этого? (непроверено) Это будет упомянутый вами «ручной способ», но он будет автоматически синхронизироваться с изменениями модели и т.п., поэтому вам не придется вручную вводить все имена атрибутов.

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:

@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

Это вернет вам клон со всеми атрибутами и без скопированных отношений.

person Jaanus    schedule 28.04.2010
comment
Это не делает глубокую копию отношений, которые хочет ОП. - person Barry Wark; 28.04.2010
comment
Да, это копирует только атрибуты. Также можно использовать propertiesByName и / или RelationsByName, но я не буду добавлять их в свой пример, потому что (в отличие от приведенного выше) я не копировал себя таким образом, и не могу за него поручиться. - person Jaanus; 28.04.2010
comment
Это также не копий, но ссылки на существующие значения атрибутов. Проще говоря, значение ObjA указывает на ObjB, что означает, что изменяемое значение влияет на ObjA. - person Yoon Lee; 02.09.2017
comment
кто просил мелкую копию? - person Aaban Tariq Murtaza; 26.07.2018

То, что вы просите, называется «глубокой копией». Поскольку это может быть очень дорогостоящим (например, при неограниченном использовании памяти) и очень сложным для правильного выполнения (учитывайте циклы в графе объектов), Core Data не предоставляет вам эту возможность.

Однако часто существует архитектура, в которой нет необходимости. Вместо того, чтобы делать копию всего графа объекта, возможно, вы можете создать новую сущность, которая инкапсулирует различия (или будущие различия), которые у вас были бы, если бы вы скопировали граф объекта, а затем ссылались бы только на исходный граф. Другими словами, создайте экземпляр новой сущности «настройщика» и не копируйте весь граф объекта. Например, рассмотрим набор рядных домов. Каждый из них имеет идентичный каркас и технику, но владелец может выбрать краску и мебель. Вместо полного копирования всего графа дома для каждого владельца создайте объект «картина и мебель», который ссылается на владельца и модель дома, для каждого владельца.

person Barry Wark    schedule 28.04.2010

Вот мой быстрый 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
    }
person Bisca    schedule 15.05.2017
comment
copyRelations Bool используется, чтобы избежать копирования обратных отношений в нашем объекте - person Bisca; 17.05.2017
comment
Почему вы называете это мелкой копией? - person Zia; 13.07.2017
comment
Какое имя вы предлагаете? Я назвал мелким, потому что это не полная копия. Если у связанных сущностей есть другое отношение (не только двунаправленное), то они не будут скопированы. Этот метод работает в некоторых случаях, но вы можете изменить его по своему усмотрению. - person Bisca; 14.07.2017
comment
Ах, в этом есть смысл. Идеальное имя. - person Zia; 14.07.2017
comment
Этот ответ великолепен и точно соответствует МОИМ потребностям, но не отвечает на исходный вопрос, который касался дублирования всего дерева взаимосвязанных NSManagedObject. Я думаю, что другие ответы тоже ошибались (из-за обратных отношений и других круговых отношений, а также возможности снова и снова встречаться с одним и тем же объектом через косвенные отношения - я действительно считаю, что OP не хотел делать дубликат этого вечного времени ... - person Motti Shneor; 16.01.2018
comment
вопрос не имел отношения к мелкой копии. - person Aaban Tariq Murtaza; 26.07.2018

Это называется «глубокой копией». Поскольку это может быть на удивление дорого, многие языки / библиотеки не поддерживают его из коробки и требуют, чтобы вы использовали собственный. К сожалению, какао - одно из них.

person Chuck    schedule 28.04.2010
comment
да. ... удивительно дорого ... как при неограниченном использовании памяти. Библиотека не может делать это автоматически, не позволяя вам легко выстрелить себе в ногу. - person Barry Wark; 28.04.2010

Также:

[clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[properties allKeys]]];
[clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[attributes allKeys]]];
person mixage    schedule 31.08.2010
comment
Что такое properties и attributes? Как их получить? - person kelin; 15.04.2017

Если вы хотите связать сущности только в иерархии отношений, вам нужно только добавить следующий код в решение Дмитрия.

Между этим

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

ЗДЕСЬ if ([namesOfEntitiesToExclude containsObject: entityName]) {

NSMutableArray *arrayToOnlyRelate   = [NSMutableArray arrayWithObjects:@"ENTITY 1",@"ENTITY 2",@"ENTITY 3", nil];

if ([arrayToOnlyRelate containsObject:entityName]) {
    return self;
}
person Kurt57    schedule 20.02.2015

Мое мнение об этом находится на https://gist.github.com/jpmhouston/7958fceae9216f69178d4719a3492525

  • передает rel.inverseRelationship.name в рекурсивный метод, чтобы не посещать обратные отношения, а не поддерживать набор alreadyCopied объектов

  • мелкие или глубокие копии

  • принимает ключевые пути отношений, чтобы не клонировать, но либо опускает, либо просто копирует, если обратное отношение ко многим

  • обходной путь для упорядоченных отношений ко многим, заканчивающихся в обратном порядке - просто перебирайте исходные объекты назад :) я не уверен, хорошая ли это идея или даже работает ли она все время

Отзывы и комментарии приветствуются, особенно если кто-то может уточнить комментарий Бенджона о неправильном заказе above "Чтобы решить эту проблему, нужно создать полный упорядоченный набор, а затем назначить его с помощью примитивного варианта KVO." и может улучшить мои заказал обходной путь для многих.

Кроме того, я использую MagicalRecord, и мой код предполагает это, включая предоставление простых методов, использующих контекст по умолчанию.

person Pierre Houston    schedule 31.05.2016

Версия Swift 4.0

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
    }

}
person Aaban Tariq Murtaza    schedule 26.07.2018

Это небольшое улучшение по сравнению с участием @Geoff H, @Derrick, @Dmitry Makarenko и @masonk. Код использует более функциональный стиль и выдает дружественные ошибки ????.

Список возможностей:

  • Обрабатывает отношения как один к одному, так и один ко многим
  • Обрабатывает упорядоченные отношения
  • Копирует весь NSManagedObject график (используя alreadyCopied кеш)
  • Реализовано как расширение NSManagedObject
  • НОВИНКА: выдает пользовательский DeepCopyError вместо сбоя с fatalError()
  • НОВИНКА: при желании извлекает NSManagedObjectContext из самого NSManagedObject

Код Swift 5.0

ВНИМАНИЕ: этот код протестирован только частично!

import CoreData

extension NSManagedObject {
    
    enum DeepCopyError: Error {
        case missingContext
        case missingEntityName(NSManagedObject)
        case unmanagedObject(Any)
    }
    
    func deepcopy(context: NSManagedObjectContext? = nil) throws -> NSManagedObject {
        
        if let context = context ?? managedObjectContext {
            
            var cache = Dictionary<NSManagedObjectID, NSManagedObject>()
            return try deepcopy(context: context, cache: &cache)
            
        } else {
            throw DeepCopyError.missingContext
        }
    }

    private func deepcopy(context: NSManagedObjectContext, cache alreadyCopied: inout Dictionary<NSManagedObjectID, NSManagedObject>) throws -> NSManagedObject {
                
        guard let entityName = self.entity.name else {
            throw DeepCopyError.missingEntityName(self)
        }
        
        if let storedCopy = alreadyCopied[self.objectID] {
            return storedCopy
        }

        let cloned = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context)
        alreadyCopied[self.objectID] = cloned
        
        // Loop through all attributes and assign then to the clone
        NSEntityDescription
            .entity(forEntityName: entityName, in: context)?
            .attributesByName
            .forEach { attribute in
                cloned.setValue(value(forKey: attribute.key), forKey: attribute.key)
            }
        
        // Loop through all relationships, and clone them.
        try NSEntityDescription
            .entity(forEntityName: entityName, in: context)?
            .relationshipsByName
            .forEach { relation in
                
                if relation.value.isToMany {
                    if relation.value.isOrdered {
                        
                        // Get a set of all objects in the relationship
                        let sourceSet = mutableOrderedSetValue(forKey: relation.key)
                        let clonedSet = cloned.mutableOrderedSetValue(forKey: relation.key)
                        
                        for object in sourceSet.objectEnumerator() {
                            if let relatedObject = object as? NSManagedObject {
                                
                                // Clone it, and add clone to the set
                                let clonedRelatedObject = try relatedObject.deepcopy(context: context, cache: &alreadyCopied)
                                clonedSet.add(clonedRelatedObject as Any)
                                
                            } else {
                                throw DeepCopyError.unmanagedObject(object)
                            }
                        }
                        
                    } else {
                        
                        // Get a set of all objects in the relationship
                        let sourceSet = mutableSetValue(forKey: relation.key)
                        let clonedSet = cloned.mutableSetValue(forKey: relation.key)
                        
                        for object in sourceSet.objectEnumerator() {
                            if let relatedObject = object as? NSManagedObject {
                                
                                // Clone it, and add clone to the set
                                let clonedRelatedObject = try relatedObject.deepcopy(context: context, cache: &alreadyCopied)
                                clonedSet.add(clonedRelatedObject as Any)
                                
                            } else {
                                throw DeepCopyError.unmanagedObject(object)
                            }
                        }
                    }
                    
                } else if let relatedObject = self.value(forKey: relation.key) as? NSManagedObject {
                    
                    // Clone it, and assign then to the clone
                    let clonedRelatedObject = try relatedObject.deepcopy(context: context, cache: &alreadyCopied)
                    cloned.setValue(clonedRelatedObject, forKey: relation.key)
                    
                }
            }
        
        return cloned
    }
}
person Alexander Braekevelt    schedule 06.04.2021