如何使用 copyWithZone 进行深拷贝以复制结构?
How to make a deep copy with copyWithZone to duplicate a structure?
我有一个代表结构的class。
这个名为 Object
的 class 具有以下属性
@property (nonatomic, strong) NSArray *children;
@property (nonatomic, assign) NSInteger type;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id parent;
children
是其他 Object
的数组。 parent
是对父对象的弱引用。
我正在尝试复制并粘贴此结构的一个分支。如果选择了根对象,显然 parent
为 nil。如果对象不是根,则它有一个父对象。
要做到这一点,Object
种类的对象必须符合 NSCopying
和 NSCoding
协议。
这是我在 class 上实现的这些协议。
-(id) copyWithZone: (NSZone *) zone
{
Object *obj = [[Object allocWithZone:zone] init];
if (obj) {
[obj setChildren:_children];
[obj setType:_type];
[obj setName:_name];
[obj setParent:_parent];
}
return obj;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:@(self.type) forKey:@"type"];
[coder encodeObject:self.name forKey:@"name"];
NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];
[coder encodeObject:childrenData forKey:@"children"];
[coder encodeConditionalObject:self.parent forKey:@"parent"]; //*
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_type = [[coder decodeObjectForKey:@"type"] integerValue];
_name = [coder decodeObjectForKey:@"name"];
_parent = [coder decodeObjectForKey:@"parent"]; //*
NSData *childrenData = [coder decodeObjectForKey:@"children"];
_children = [NSKeyedUnarchiver unarchiveObjectWithData:childrenData];
_parent = nil;
}
return self;
}
您可能已经注意到,我没有参考在 initWithCoder:
和 encodeWithCoder:
上检索或存储 self.parent
,因此,对象的每个子对象都带有 parent =无。
我只是不知道如何存储它。仅仅因为这个。假设我有这个Object
的结构。
ObjectA > ObjectB > ObjectC
当 encoderWithCoder:
开始它的魔术编码 ObjectA
时,它也会编码 ObjectB
和 ObjectC
但是当它开始编码 ObjectB
时,它会发现一个父引用指向 ObjectA
并将再次启动它,创建一个挂起应用程序的循环引用。我试过了。
我如何encode/restore那个父引用?
我需要的是存储一个对象,并在还原时还原一个与存储的对象相同的新副本。我不想恢复存储的同一个对象,而是一个副本。
注意:我按照 Ken 的建议添加了标有 //*
的行,但是 _parent
在 initWithCoder:
上为零应该有一个 parent
用 [coder encodeConditionalObject:self.parent forKey:@"parent"]
编码 parent。正常使用 -decodeObjectForKey:
解码。
这样做的目的是,如果 parent 由于某些其他原因被存档——假设它是树中更高 object 的 child——对parent 已恢复。但是,如果 parent 仅被编码为条件 object,它将不会存储在存档中。解码存档后,parent 将是 nil
。
您对 children
数组进行编码的方式既笨拙,又会妨碍将 parent 正确编码为条件 object。由于您正在为 children 创建一个单独的存档,因此没有存档可能同时具有 Object
及其 parent(无条件)。因此,link 到 parent 不会在存档解码时恢复。您应该改为 [coder encodeObject:self.children forKey:@"children"]
和 _children = [coder decodeObjectForKey:@"children"]
。
您的 -copyWithZone:
实施存在问题。副本与原件具有相同的 children,但 children 不认为副本是他们的 parent。同样,副本将原件的 parent 视为其 parent,但 parent object 不包括其 children 中的副本。这会让你伤心。
一种选择是利用您的 NSCoding
支持来制作副本。您会对原件进行编码,然后对其进行解码以生成副本。像这样:
-(id) copyWithZone: (NSZone *) zone
{
NSData* selfArchive = [NSKeyedArchiver archivedDataWithRootObject:self];
return [NSKeyedUnarchiver unarchiveObjectWithData:selfArchive];
}
复制操作将复制整个 sub-tree。因此,它将有自己的 children,这是原始 children 等的副本。它不会有 parent.
另一种选择是只复制原始文件中不属于包含树数据结构的部分(即 type
和 name
)。也就是说,副本将以没有 parent 和 children 结束,这是合适的,因为它不在树本身中,它只是碰巧在树中的东西的副本时间。
最后,-copyWithZone:
在分配新的 object 时应该使用 [self class]
而不是 Object
。这样,如果您在设置子 class 的属性之前编写 Object
的子 class 及其 -copyWithZone:
调用 super
,则此实现在Object
中会分配一个实例对class(subclass)。例如:
-(id) copyWithZone: (NSZone *) zone
{
Object *obj = [[[self class] allocWithZone:zone] init];
if (obj) {
[obj setType:_type];
[obj setName:_name];
}
return obj;
}
在您编辑问题后添加注释
@KenThomases 本质上是正确的,但他的问题
By the way, is there are a reason you're encoding the children
array that way?
应该是更正而不是问题。方法 encodeConditionalObject:forKey:
将在当前存档 中有条件地编码 。通过使用不同的存档:
NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];
您没有实现正确的分享。你应该按照 Ken 的建议去做,只是:
[coder encodeObject:self.children forKey:@"children"];
所以 children 由相同的 NSKeyedArchiver
编码。
你的另一个问题很明显:
_parent = nil;
大概是剩菜了。
我有一个代表结构的class。
这个名为 Object
的 class 具有以下属性
@property (nonatomic, strong) NSArray *children;
@property (nonatomic, assign) NSInteger type;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id parent;
children
是其他 Object
的数组。 parent
是对父对象的弱引用。
我正在尝试复制并粘贴此结构的一个分支。如果选择了根对象,显然 parent
为 nil。如果对象不是根,则它有一个父对象。
要做到这一点,Object
种类的对象必须符合 NSCopying
和 NSCoding
协议。
这是我在 class 上实现的这些协议。
-(id) copyWithZone: (NSZone *) zone
{
Object *obj = [[Object allocWithZone:zone] init];
if (obj) {
[obj setChildren:_children];
[obj setType:_type];
[obj setName:_name];
[obj setParent:_parent];
}
return obj;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:@(self.type) forKey:@"type"];
[coder encodeObject:self.name forKey:@"name"];
NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];
[coder encodeObject:childrenData forKey:@"children"];
[coder encodeConditionalObject:self.parent forKey:@"parent"]; //*
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_type = [[coder decodeObjectForKey:@"type"] integerValue];
_name = [coder decodeObjectForKey:@"name"];
_parent = [coder decodeObjectForKey:@"parent"]; //*
NSData *childrenData = [coder decodeObjectForKey:@"children"];
_children = [NSKeyedUnarchiver unarchiveObjectWithData:childrenData];
_parent = nil;
}
return self;
}
您可能已经注意到,我没有参考在 initWithCoder:
和 encodeWithCoder:
上检索或存储 self.parent
,因此,对象的每个子对象都带有 parent =无。
我只是不知道如何存储它。仅仅因为这个。假设我有这个Object
的结构。
ObjectA > ObjectB > ObjectC
当 encoderWithCoder:
开始它的魔术编码 ObjectA
时,它也会编码 ObjectB
和 ObjectC
但是当它开始编码 ObjectB
时,它会发现一个父引用指向 ObjectA
并将再次启动它,创建一个挂起应用程序的循环引用。我试过了。
我如何encode/restore那个父引用?
我需要的是存储一个对象,并在还原时还原一个与存储的对象相同的新副本。我不想恢复存储的同一个对象,而是一个副本。
注意:我按照 Ken 的建议添加了标有 //*
的行,但是 _parent
在 initWithCoder:
上为零应该有一个 parent
用 [coder encodeConditionalObject:self.parent forKey:@"parent"]
编码 parent。正常使用 -decodeObjectForKey:
解码。
这样做的目的是,如果 parent 由于某些其他原因被存档——假设它是树中更高 object 的 child——对parent 已恢复。但是,如果 parent 仅被编码为条件 object,它将不会存储在存档中。解码存档后,parent 将是 nil
。
您对 children
数组进行编码的方式既笨拙,又会妨碍将 parent 正确编码为条件 object。由于您正在为 children 创建一个单独的存档,因此没有存档可能同时具有 Object
及其 parent(无条件)。因此,link 到 parent 不会在存档解码时恢复。您应该改为 [coder encodeObject:self.children forKey:@"children"]
和 _children = [coder decodeObjectForKey:@"children"]
。
您的 -copyWithZone:
实施存在问题。副本与原件具有相同的 children,但 children 不认为副本是他们的 parent。同样,副本将原件的 parent 视为其 parent,但 parent object 不包括其 children 中的副本。这会让你伤心。
一种选择是利用您的 NSCoding
支持来制作副本。您会对原件进行编码,然后对其进行解码以生成副本。像这样:
-(id) copyWithZone: (NSZone *) zone
{
NSData* selfArchive = [NSKeyedArchiver archivedDataWithRootObject:self];
return [NSKeyedUnarchiver unarchiveObjectWithData:selfArchive];
}
复制操作将复制整个 sub-tree。因此,它将有自己的 children,这是原始 children 等的副本。它不会有 parent.
另一种选择是只复制原始文件中不属于包含树数据结构的部分(即 type
和 name
)。也就是说,副本将以没有 parent 和 children 结束,这是合适的,因为它不在树本身中,它只是碰巧在树中的东西的副本时间。
最后,-copyWithZone:
在分配新的 object 时应该使用 [self class]
而不是 Object
。这样,如果您在设置子 class 的属性之前编写 Object
的子 class 及其 -copyWithZone:
调用 super
,则此实现在Object
中会分配一个实例对class(subclass)。例如:
-(id) copyWithZone: (NSZone *) zone
{
Object *obj = [[[self class] allocWithZone:zone] init];
if (obj) {
[obj setType:_type];
[obj setName:_name];
}
return obj;
}
在您编辑问题后添加注释
@KenThomases 本质上是正确的,但他的问题
By the way, is there are a reason you're encoding the
children
array that way?
应该是更正而不是问题。方法 encodeConditionalObject:forKey:
将在当前存档 中有条件地编码 。通过使用不同的存档:
NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];
您没有实现正确的分享。你应该按照 Ken 的建议去做,只是:
[coder encodeObject:self.children forKey:@"children"];
所以 children 由相同的 NSKeyedArchiver
编码。
你的另一个问题很明显:
_parent = nil;
大概是剩菜了。