如何使用copyWithZone制作深层副本来复制结构?

时间:2015-03-03 03:51:31

标签: ios iphone cocoa nscoding nscopying

我有一个代表结构的类。

这个名为Object的类具有以下属性

@property (nonatomic, strong) NSArray *children;
@property (nonatomic, assign) NSInteger type;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id parent;

children是其他Object的数组。 parent是对象父级的弱引用。

我正在尝试复制并粘贴此结构的分支。如果选择了根对象,显然parent为零。如果对象不是根,则它具有父对象。

为了能够这样做,Object种类的对象必须符合NSCopyingNSCoding协议。

这是我在该类上实现的这些协议。

-(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;

}

您可能已经注意到我没有在self.parentinitWithCoder:上检索或存储encodeWithCoder:的参考,因此,对象的每个子对象都带有parent = nil。

我根本不知道如何存储它。正因为如此。假设我有Object的结构。

ObjectA > ObjectB > ObjectC

encoderWithCoder:启动其魔术编码ObjectA时,它还会对ObjectBObjectC进行编码,但当它开始编码ObjectB时,它会找到父参考指向ObjectA并将再次启动,创建一个挂起应用程序的循环引用。我试过了。

如何编码/恢复该父参考?

我需要的是存储一个对象,并在恢复时恢复一个与存储的相同的新副本。我不想恢复存储的同一个对象,而是一个副本。

注意:我已根据Ken的建议添加了标有//*的行,但_parent上的initWithCoder:对于应该有{的对象是{0} {1}}

2 个答案:

答案 0 :(得分:4)

使用[coder encodeConditionalObject:self.parent forKey:@"parent"]对父级进行编码。正常使用-decodeObjectForKey:对其进行解码。

这样做,如果父母因其他原因而被归档 - 比如它是树中较高对象的孩子 - 则会恢复对父母的引用。但是,如果父级只被编码为条件对象,则它不会存储在存档中。解码归档后,父级将为nil

您对children数组进行编码的方式既笨拙又会阻止将父进程正确编码为条件对象。由于您要为子项创建单独的存档,因此没有存档可能同时具有Object及其父项(无条件)。因此,解码归档时将不会恢复到父级的链接。您应该改为[coder encodeObject:self.children forKey:@"children"]_children = [coder decodeObjectForKey:@"children"]

您的-copyWithZone:实施存在问题。副本与原始副本具有相同的子级,但子级不认为副本为其父级。同样,副本将原始父级视为其父级,但该父级对象不包括其子级中的副本。这会让你感到悲伤。

一种选择是利用您的NSCoding支持来制作副本。您将对原始文件进行编码,然后对其进行解码以生成副本。像这样:

-(id) copyWithZone: (NSZone *) zone
{
  NSData* selfArchive = [NSKeyedArchiver archivedDataWithRootObject:self];
  return [NSKeyedUnarchiver unarchiveObjectWithData:selfArchive];
}

复制操作将复制整个子树。所以,它会有自己的孩子,它们是原始孩子的副本等等。它没有父母。

另一种选择是仅复制不属于包含树数据结构的原始方面(即typename)。也就是说,副本最终没有父项,也没有子项,这是合适的,因为它不在树本身,它只是当时碰巧在树中的事物的副本。

最后,-copyWithZone:在分配新对象时应使用[self class]而不是Object。这样,如果您在设置子类的属性之前编写了Object的子类及其-copyWithZone:调用到super,那么Object中的此实现将分配一个实例右类(子类)。例如:

-(id) copyWithZone: (NSZone *) zone
{
  Object *obj = [[[self class] allocWithZone:zone] init];
  if (obj) {
    [obj setType:_type];
    [obj setName:_name];
   }

  return obj;
}

答案 1 :(得分:1)

在您添加注释

的问题编辑后

@KenThomases基本上是正确的,但是他的问题

  

顺便说一句,您是否有理由以这种方式对children数组进行编码?

应该是纠正而不是问题。方法encodeConditionalObject:forKey:将有条件地编码当前存档中的 。通过使用不同的存档:

NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];

您尚未实现正确的分享。你应该像肯建议那样做:

[coder encodeObject:self.children forKey:@"children"];

因此孩子们使用相同的NSKeyedArchiver进行编码。

你的另一个问题是相当明显的:

_parent = nil;

大概是剩下的。