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

У меня есть класс, представляющий структуру.

Этот класс с именем 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, и запускает ее снова, создавая циклическую ссылку, которая зависает в приложении. Я пробовал это.

Как мне закодировать/восстановить эту родительскую ссылку?

Что мне нужно, так это сохранить объект и к моменту восстановления восстановить новую копию, идентичную той, что была сохранена. Я не хочу восстанавливать тот же объект, который был сохранен, а скорее копию.

ПРИМЕЧАНИЕ. Я добавил строки, отмеченные //*, как предложил Кен, но _parent равно нулю на initWithCoder: для объектов, которые должны иметь parent


person Duck    schedule 03.03.2015    source источник


Ответы (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;
}
person Ken Thomases    schedule 03.03.2015
comment
Это первый раз, когда я использую эту штуку copywithZone/encode/decode. Можете ли вы показать в коде, что вы имеете в виду под четвертым и последним абзацами? Спасибо - person Duck; 03.03.2015
comment
Я предполагаю, что вы имели в виду пятый абзац, поскольку в четвертом не было ничего, что можно было бы проиллюстрировать. Я отредактировал, чтобы включить примеры реализации подходов, которые я описал. - person Ken Thomases; 03.03.2015
comment
Спасибо за объяснение, но у вас есть два кода copyWithZone, я не понимаю, что мне следует использовать... - person Duck; 03.03.2015
comment
@SpaceDog Это зависит от семантики вашей программы. Если копирование Object должно также копировать всех его предков и потомков, используйте первое. Если он должен копировать только type и name (и стать осиротевшим), используйте второй. - person Aaron Brager; 03.03.2015
comment
@AaronBrager, спасибо за разъяснение, за исключением одного: копирование Object с использованием подхода кодирования/декодирования скопирует его потомков, но никогда не родителя или предков. - person Ken Thomases; 04.03.2015

После редактирования вопроса добавление примечания

@KenThomases по существу прав, но его вопрос

Кстати, есть ли причина, по которой вы кодируете массив children таким образом?

Должна быть поправка, а не вопрос. Метод encodeConditionalObject:forKey: будет условно кодировать в текущем архиве. Используя другой архив:

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

вы не добились правильного обмена. Вы должны сделать, как предложил Кен, и просто:

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

поэтому дети кодируются одним и тем же NSKeyedArchiver.

И другая ваша проблема довольно очевидна:

_parent = nil;

предположительно остатки.

person CRD    schedule 03.03.2015