Извлечение данных из NSKeyedArchiver в коде, отличном от ARC

#objective-c #memory #nskeyedarchiver

#objective-c #память #nskeyedarchiver

Вопрос:

В моем проекте iOS, отличном от ARC, у меня есть метод, который возвращает архивированные данные:

 - (NSData*) archivedData {
    NSMutableData* data = [[NSMutableData alloc] init];
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    // Encode the fields that must be archived:
    [archiver encodeObject:... forKey:...];
    ...
    [archiver finishEncoding];
    [archiver release];
    return [data autorelease];
  

Поскольку initForWritingWithMutableData: он устарел, я изменил реализацию:

 - (NSData*) archivedData {
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
    // Encode the fields that must be archived:
    [archiver encodeObject:... forKey:...];
    ...
    [archiver finishEncoding];
    NSData* data = [archiver encodedData];
    [archiver release];
    return data;
  

Сначала я вызвал autorelease закодированный NSData объект, прежде чем вернуть его, но это привело к плохому доступу к памяти ( EXC_BAD_ACCESS ). Кажется, все работает нормально без autorelease .

Теперь я в замешательстве. Поскольку я освобождаю архиватор перед возвратом данных, я подумал autorelease , что защитит NSData объект, освободив его не до того, как он будет обработан вызывающим методом. Я беспокоился, что NSData объект может быть освобожден сразу после освобождения архиватора без autorelease вызова. Каким-то образом происходит обратное, когда я запускаю код.

Может кто-нибудь, пожалуйста, пролить свет на это поведение? Кроме того, если я делаю что-то не так, я хотел бы знать, как исправить код.

Я знаю о статическом методе archivedDataWithRootObject:requiringSecureCoding:error: , но я не могу его легко использовать, потому что у меня нет корневого объекта, поскольку я кодирую объекты по отдельности. Использование корневого объекта нарушит совместимость для существующих пользователей приложения (если я правильно понимаю).

Ответ №1:

 NSData* data = [archiver encodedData];
  

Метод -encodedData не начинается с alloc или new и не включает в себя слово copy , и оно не вызывается retain . Это означает, что вы не стали владельцем этого. Вы не должны вызывать release его. Это не принадлежит вам. Сказано по-другому: вы не поместили сохранение в этот объект; вы не должны вызывать release для него.

Затем вы освобождаете archiver , за который был ответственен data . Больше не data существует обещания. Исправление для этого — сохранить его перед выпуском archiver :

 NSData* data = [[archiver encodedData] retain];
  

Это фактически то же самое, что и исходный код, выполняемый при инициализации data с использованием [NSMutableData alloc] . Обратите внимание на слово «alloc». Это дает право собственности (сохранение) data .

В соответствии с теми же соглашениями об именовании этот метод обещает вернуть объект, который вызывающей стороне не нужно освобождать. (Часто проще всего думать об этом как о том, что количество чистых сохранений равно 0, но есть много случаев, когда это не совсем верно.)

 - (NSData*) archivedData { ... }
  

Вы поставили сохранение на data , поэтому вам нужно это сбалансировать. Однако вы хотите, чтобы объект просуществовал достаточно долго, чтобы его можно было вернуть. Исправление для этого есть -autorelease , как и в исходном коде:

 return [data autorelease];
  

Так что все вместе:

 - (NSData*) archivedData {
    NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
    // Encode the fields that must be archived:
    [archiver encodeObject:... forKey:...];
    ...
    [archiver finishEncoding];
    NSData* data = [[archiver encodedData] retain]; // Retain here
    [archiver release];
    archiver = nil; // It's good practice to nil values that are no longer valid
    return [data autorelease]; // autorelease here
}
  

Я больше не могу найти страницу полезных правил управления памятью Apple, но она кратко изложена в трех волшебных словах.

Комментарии:

1. developer.apple.com/library/archive/documentation/Cocoa/…

2. Спасибо за ваш проницательный ответ! Ваш пример кода имеет смысл и работает так, как задумано. Однако я все еще не до конца понимаю, почему возникает ошибка EXC_BAD_ACCESS, когда объект автоматически удаляется без его сохранения. Что происходит с объектом?

3. Отправка объекта -autorelease означает, что когда пул авторелиза истощается (обычно в конце цикла событий), объект будет отправлен -release . Если объект уже был уничтожен, то это сообщение будет отправлено на висячий указатель, что является неопределенным поведением, но в большинстве случаев приведет к сбою с исключением. Проще говоря, «авторелиз» — это просто «релиз в будущем», и если вы выпускаете больше, чем сохраняете, происходит сбой. (Если вы сохраняете больше, чем освобождаете, вы (как правило) пропускаете память.)