我有一个包含各种属性和关系类型的托管对象(“A”),它的关系也有自己的属性和关系。我想做的是“复制”或“复制”以对象“A”为根的整个对象图,从而创建一个与“A”非常相似的新对象“B”。
更具体地说,“B”(或其子项)包含的任何关系都不应该指向与“A”相关的对象。应该有一个全新的对象图,具有完整的相似关系,并且所有对象都具有相同的属性,但当然是不同的 id。
有一种明显的手动方法可以做到这一点,但我希望了解一种更简单的方法,这在 Core Data 文档中并不完全明显。
蒂亚!
我有一个包含各种属性和关系类型的托管对象(“A”),它的关系也有自己的属性和关系。我想做的是“复制”或“复制”以对象“A”为根的整个对象图,从而创建一个与“A”非常相似的新对象“B”。
更具体地说,“B”(或其子项)包含的任何关系都不应该指向与“A”相关的对象。应该有一个全新的对象图,具有完整的相似关系,并且所有对象都具有相同的属性,但当然是不同的 id。
有一种明显的手动方法可以做到这一点,但我希望了解一种更简单的方法,这在 Core Data 文档中并不完全明显。
蒂亚!
这是我创建的一个类,用于执行托管对象的“深拷贝”:属性和关系。请注意,这不会检查对象图中的循环。(感谢 Jaanus 的起点……)
@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
这些答案让我非常接近,尽管它们似乎确实有一些缺点:
第一,我听取了 ZS 的建议,把它放在了 NSManagedObject 上,这对我来说似乎更干净一些。
2,我的对象图包含一对一的关系,所以我从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
我已经更新了 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;
}
这是@Derricks 的答案,经过修改以支持新的iOS 6.0 通过询问关系以查看它是否已订购来订购多对多关系。当我在那里时,我为在同一个 NSManagedObjectContext 中进行克隆的常见情况添加了一个更简单的 -clone 方法。
//
// 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
斯威夫特 5
这建立在@Derrick 和@Dmitry Makarenko 和@masonk 的贡献之上,将所有东西整合在一起,对其进行改进并将其转变为适合 2020 年的解决方案。
.
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)
我注意到当前答案存在一些错误。首先,在迭代时,某些东西似乎正在改变多对多相关对象的集合。其次,我不确定 API 中是否发生了某些变化,但是使用NSRelationshipDescription
's String 表示作为键在抓取这些相关对象时会引发异常。
我做了一些调整,做了一些基本的测试,它似乎工作。如果有人想进一步调查,那就太好了!
@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
我已经修改了Derrick 的答案,这对我来说非常有效,以支持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
像这样的东西?(未经测试)这将是您提到的“手动方式”,但它会自动与模型更改同步,因此您不必手动输入所有属性名称。
斯威夫特 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
这将为您返回一个包含所有属性且没有复制关系的克隆。
我确实需要解决@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
您所要求的称为“深拷贝”。因为它可能非常昂贵(如无限的内存使用)并且很难正确处理(考虑对象图中的循环),Core Data 没有为您提供这个工具。
然而,通常有一种架构可以避免这种需要。除了复制整个对象图之外,也许您可以创建一个新实体来封装您在复制对象图然后仅引用原始图时会产生的差异(或未来差异)。换句话说,实例化一个新的“customizer”实体并且不要复制整个对象图。例如,考虑一组排屋。每个都有相同的框架和电器,但业主可以定制油漆和家具。与其为每个所有者深度复制整个房屋图表,不如为每个所有者拥有一个引用所有者和房屋模型的“绘画和家具”实体。
在这里,您有我的swift 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
}
这称为“深拷贝”。因为它可能非常昂贵,所以许多语言/库不支持开箱即用,需要您自己动手。不幸的是,可可就是其中之一。
还:
[clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[properties allKeys]]];
[clone setValuesForKeysWithDictionary:[item dictionaryWithValuesForKeys:[attributes allKeys]]];
如果您只想关联关系层次结构中的实体,您只需将以下代码添加到 Dmitry 的解决方案中
这之间
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;
}
我对此的看法是https://gist.github.com/jpmhouston/7958fceae9216f69178d4719a3492577
传递rel.inverseRelationship.name
到递归方法以省略访问反向关系而不是维护一组alreadyCopied
对象
浅拷贝或深拷贝
接受不克隆的关系的关键路径,但如果逆是一对多关系,则要么省略,要么简单地复制
有序的多对多关系以倒序结束的解决方法 - 只需向后迭代源实体:)我不确定这是否是个好主意,或者它是否一直有效
欢迎反馈和评论,特别是如果有人可以详细说明Benjohn对上述错误排序的评论“解决此问题的方法是构建完整的有序集,然后使用原始 KVO 变体进行分配。 ”并且可以改进我的排序,以-许多解决方法。
此外,我正在使用 MagicalRecord,因此我的代码假定了这一点,包括提供使用其默认上下文的简单方法。
斯威夫特 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
}
}
这是对@Geoff H、@Derrick、@Dmitry Makarenko 和@masonk 贡献的轻微改进。该代码使用更实用的样式并引发友好的错误。
NSManagedObject
图(使用alreadyCopied
缓存)NSManagedObject
DeepCopyError
而不是崩溃fatalError()
NSManagedObjectContext
从NSManagedObject
自身检索警告:此代码仅经过部分测试!
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
}
}