我有一个代表结构的类。
这个名为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
种类的对象必须符合NSCopying
和NSCoding
协议。
这是我在该类上实现的这些协议。
-(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.parent
和initWithCoder:
上检索或存储encodeWithCoder:
的参考,因此,对象的每个子对象都带有parent = nil。
我根本不知道如何存储它。正因为如此。假设我有Object
的结构。
ObjectA > ObjectB > ObjectC
当encoderWithCoder:
启动其魔术编码ObjectA
时,它还会对ObjectB
和ObjectC
进行编码,但当它开始编码ObjectB
时,它会找到父参考指向ObjectA
并将再次启动,创建一个挂起应用程序的循环引用。我试过了。
如何编码/恢复该父参考?
我需要的是存储一个对象,并在恢复时恢复一个与存储的相同的新副本。我不想恢复存储的同一个对象,而是一个副本。
注意:我已根据Ken的建议添加了标有//*
的行,但_parent
上的initWithCoder:
对于应该有{的对象是{0} {1}}
答案 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];
}
复制操作将复制整个子树。所以,它会有自己的孩子,它们是原始孩子的副本等等。它没有父母。
另一种选择是仅复制不属于包含树数据结构的原始方面(即type
和name
)。也就是说,副本最终没有父项,也没有子项,这是合适的,因为它不在树本身,它只是当时碰巧在树中的事物的副本。
最后,-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;
大概是剩下的。