4

我有一个代表结构的类。

此类称为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;

}

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

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

ObjectA > ObjectB > ObjectC

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

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

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

注意:我已经//*按照 Ken 的建议添加了标记的行,但是_parent对于initWithCoder:应该具有parent

4

2 回答 2

4

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

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

您对children数组进行编码的方式既笨拙,又会阻止将父级正确编码为条件对象。由于您正在为子级创建单独的存档,因此没有存档可能同时具有 anObject及其父级(无条件)。因此,当档案被解码时,到父级的链接将不会被恢复。你应该做[coder encodeObject:self.children forKey:@"children"]and _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;
}
于 2015-03-03T04:49:21.607 回答
1

在您编辑问题后添加注释

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

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

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

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

你没有实现正确的分享。你应该按照肯的建议做,只是:

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

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

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

_parent = nil;

想必是剩下的。

于 2015-03-03T19:21:21.333 回答