#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
. Если объект уже был уничтожен, то это сообщение будет отправлено на висячий указатель, что является неопределенным поведением, но в большинстве случаев приведет к сбою с исключением. Проще говоря, «авторелиз» — это просто «релиз в будущем», и если вы выпускаете больше, чем сохраняете, происходит сбой. (Если вы сохраняете больше, чем освобождаете, вы (как правило) пропускаете память.)