Как сделать глубокую копию с copyWithZone, чтобы дублировать структуру?


У меня есть класс, который представляет структуру.

Этот класс, называемый 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, очевидно, равен нулю. Если объект не является корнем, у него есть родитель.

, чтобы быть в состоянии сделать так, объекты вида 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 4

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;

Предположительно, что-то осталось.