如何使用 copyWithZone 进行深层复制以复制结构?

Posted

技术标签:

【中文标题】如何使用 copyWithZone 进行深层复制以复制结构?【英文标题】:How to make a deep copy with copyWithZone to duplicate a structure? 【发布时间】:2015-03-03 03:51:30 【问题描述】:

我有一个代表结构的类。

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

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

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

我正在尝试复制和粘贴此结构的一个分支。如果选择了根对象,parent 显然是 nil。如果对象不是根,它有一个父对象。

为此,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;


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

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

ObjectA > ObjectB > ObjectC

encoderWithCoder: 开始它的魔法编码ObjectA 时,它也会编码ObjectBObjectC,但是当它开始编码ObjectB 时,它会找到一个指向ObjectA 的父引用并重新开始,创建一个挂起应用程序的循环引用。我试过了。

如何编码/恢复该父引用?

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

注意:我已按照 Ken 的建议添加了标有 //* 的行,但是对于应该具有 parent 的对象,_parentinitWithCoder: 上为零。

【问题讨论】:

【参考方案1】:

使用[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;

【讨论】:

这是我第一次使用这个 copywithZone/encode/decode 的东西。你能用代码说明第四段和最后一段的意思吗?谢谢 我假设您的意思是第五段,因为第四段没有任何说明。我进行了编辑,以包含我描述的方法的示例实现。 感谢您的解释,但是您有两个代码 copyWithZone,我不明白应该使用什么... @SpaceDog 这取决于你程序的语义。如果复制 Object 还应该复制它的所有祖先和后代,请使用第一个。如果它应该只复制typename(并成为孤儿),请使用第二个。 @AaronBrager,感谢您的澄清,除了一件事:使用编码/解码方法复制 Object 将复制其后代,但不会复制其父或祖先。【参考方案2】:

在您编辑问题后添加注释

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

顺便问一下,您以这种方式编码 children 数组是否有原因?

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

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

您没有实现正确的共享。你应该按照 Ken 的建议去做,然后:

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

所以孩子们被相同的NSKeyedArchiver编码。

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

_parent = nil;

大概是剩下的。

【讨论】:

以上是关于如何使用 copyWithZone 进行深层复制以复制结构?的主要内容,如果未能解决你的问题,请参考以下文章

如何使Java中的InputStream的深层复制

如何在 Java 中制作 ArrayList<Integer> 的深层副本? [复制]

C#从对象内部进行深层复制[复制]

OC中对象的复制:二

OC中对象的复制:二

在url中哈希以与ajax进行深层链接