Как сделать глубокую копию с copyWithZone, чтобы дублировать структуру?
У меня есть класс, который представляет структуру.
Этот класс, называемый Object
, обладает следующими свойствами
@property (nonatomic, strong) NSArray *children;
@property (nonatomic, assign) NSInteger type;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id parent;
children
является массивом других Object
s. 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
, и запускает ее снова, создавая циклическую ссылку, которая вешает приложение. Я уже пробовал.
Как мне закодировать / восстановить эту родительскую ссылку?
Что мне нужно, так это сохранить объект и к моменту восстановления восстановить новую копию, идентичную той, что была сохранена. Я не хочу восстанавливать тот же самый объект, который был сохранен, а скорее, копию.Примечание : я добавил линии, помеченные //*
, как предложил Кен, но _parent
равно нулю на initWithCoder:
для объектов, которые должны иметь parent
2 ответа:
Закодируйте родителя с помощью
Это приводит к тому, что если родитель был бы заархивирован по какой - то другой причине - скажем, это был потомок объекта выше в дереве-ссылка на родителя восстанавливается. Однако если родитель был закодирован только как условный объект, он не будет сохранен в архиве. После декодирования архива родителем будет[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; }
после редактирования вопроса добавьте Примечание
@KenThomases по существу прав, но его Вопрос
Кстати, есть ли причина, по которой вы кодируете массив
children
таким образом?Должна быть поправка, а не вопрос. Метод
encodeConditionalObject:forKey:
будет условно кодировать в текущем архиве. Используя другой архив:NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];
Вы не достигли правильного обмена. Вы должны сделать так, как предложил Кен и просто:
Таким образом, дети кодируются одним и тем же[coder encodeObject:self.children forKey:@"children"];
NSKeyedArchiver
.И ваша другая проблема довольно очевидна:
_parent = nil;
Предположительно, что-то осталось.