Использовать автозапуск перед добавлением объектов в коллекцию?

Я просматривал вопросы, заданные на StackOverflow, но их так много об управлении памятью в Objective-C, что я не смог найти ответ, который искал.

Вопрос в том, можно ли (и рекомендуется ли) вызывать autorelease перед добавлением вновь созданного объекта в коллекцию (например, NSMutableArray)? Или я должен выпустить его явно после добавления. (Я знаю, что NSMutableArray сохранит объект)

Это иллюстрирует мой вопрос:

Сценарий A (автоматический выпуск):

- (void) add {
   // array is an instance of NSMutableArray

   MyClass *obj = [[[MyClass alloc] init] autorelease];

   [array addObject:obj];
}

Сценарий B (явное освобождение):

- (void) add {
   // array is an instance of NSMutableArray

   MyClass *obj = [[MyClass alloc] init];

   [array addObject:obj];

   [obj release];
}

Я предполагаю, что оба правильны, но я не уверен, и я точно не знаю, что из себя представляет предпочтительный способ.

Могут ли гуру Objective-C пролить свет на это?


person Philippe Leybaert    schedule 18.07.2009    source источник


Ответы (7)


Оба верны и будут работать так, как вы от них ожидаете.

Я лично предпочитаю использовать последний метод, но только потому, что мне нравится четко указывать, когда объекты будут выпущены. Освобождая объект автоматически, мы всего лишь говорим: «Этот объект будет выпущен в произвольный момент в будущем». Это означает, что вы можете поместить автоматически выпущенный объект в массив, уничтожить массив, и объект может (вероятно) все еще существовать.

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

person Dave DeLong    schedule 18.07.2009
comment
Если вы хотите, чтобы ваш код был абсолютно безопасным, настоятельно рекомендуется использовать автозапуск: см. stackoverflow.com/questions/1147785/ - person Casebash; 14.01.2010

ИМХО, какой из вариантов «правильный» - это вопрос предпочтений. Я не возражаю против респондентов, которые выступают за отказ от использования autorelease, но я предпочитаю использовать autorelease, если только нет веских причин не делать этого. Я перечислю свои причины, и вы сможете решить, подходят ли они вашему стилю программирования.

Как отметил Чак, существует легенда о том, что использование пулов с автоматическим выпуском связано с некоторыми накладными расходами. Это далеко от истины, и это результат бесчисленных часов, потраченных на Shark.app, чтобы выжать из кода последнюю каплю производительности. Попытка оптимизировать для этого глубоко уклоняется от «преждевременной оптимизации». Если и только если Shark.app предоставит вам достоверные данные, это может быть проблемой, если вы даже подумайте о том, чтобы заглянуть в него.

Как указывали другие, автоматически выпущенный объект «освобождается в какой-то более поздний момент». Это означает, что они задерживаются, занимая память, пока не наступит «более поздний момент». В «большинстве» случаев это находится в конце этапа обработки события, прежде чем цикл выполнения перейдет в режим ожидания до следующего события (таймера, щелчка пользователем чего-либо и т. Д.).

Однако иногда вам нужно будет избавиться от этих временных объектов раньше, чем позже. Например, вам нужно обработать огромный файл размером в несколько мегабайт или десятки тысяч строк из базы данных. Когда это произойдет, вам нужно будет поместить NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; в хорошо выбранную точку, а затем [pool release]; внизу. Это почти всегда происходит при некоторой «пакетной обработке цикла», поэтому обычно это начало и конец какого-то критического цикла. Опять же, это должно быть основано на доказательствах, а не на догадках. ObjectAlloc Instrument.app - это то, что вы используете для поиска этих проблемных мест.

Однако основная причина, по которой я предпочитаю autorelease release, заключается в том, что писать программы без утечек намного проще. Короче говоря, если вы решите пойти по маршруту release, вам необходимо гарантировать, что release в конечном итоге будет отправлен на obj при всех обстоятельствах. Хотя это может показаться простым, на самом деле это удивительно сложно сделать на практике. Возьмем, к примеру, ваш пример:

   // array is an instance of NSMutableArray
   MyClass *obj = [[MyClass alloc] init];
   [array addObject:obj];
   // Assume a few more lines of work....
   [obj release];

Теперь представьте, что по какой-то причине что-то где-то тонко нарушает ваше предположение, что array является изменяемым, возможно, в результате использования какого-либо метода для обработки результатов, и возвращенный массив, содержащий обработанные результаты, был создан как NSArray. Когда вы отправляете addObject: этому неизменяемому NSArray, выдается исключение, и вы никогда не отправите obj его release сообщение. Или, может быть, что-то пойдет не так где-то между моментом, когда obj было allocd и обязательным вызовом release, например, вы проверяете какое-то условие, и return() сразу по ошибке, потому что вам не пришло в голову, что этот вызов release позже должен иметь место.

Вы только что слили объект. И, вероятно, подписались на несколько дней, чтобы выяснить, где и почему вы его сливаете. По опыту, вы потратите много часов, глядя на этот код выше, убежденные, что он не может быть источником утечки, потому что вы очень четко отправляете obj a release. Затем, через несколько дней, вы испытаете то, что можно описать только как религиозное прозрение, когда вы поймете причину проблемы.

Рассмотрим случай autorelease:

   // array is an instance of NSMutableArray
   MyClass *obj = [[[MyClass alloc] init] autorelease];
   [array addObject:obj];
   // Assume a few more lines of work....

Теперь уже не имеет значения, что произойдет, потому что случайно obj утечка практически невозможна даже в чрезвычайно необычных или исключительных случаях.

person johne    schedule 19.07.2009
comment
Это возникло в моем новом вопросе здесь: stackoverflow.com/questions/2911678. Отличный момент. - person Dan Rosenstark; 26.05.2010
comment
[pool drain] может показаться лучшим выбором в случае, если iOS когда-либо получит GC - person Chris Wagner; 22.11.2010

Они оба верны, но B может быть предпочтительнее, потому что у него совсем нет накладных расходов. Autorelease заставляет пул autorelease управлять объектом. Это имеет очень небольшие накладные расходы, которые, конечно, умножаются на количество задействованных объектов.

Таким образом, с одним объектом A и B более или менее одинаковы, но определенно не используйте A в сценариях с большим количеством объектов для добавления в массив.

В разных ситуациях автозапуск может задерживать и накапливать освобождение многих объектов в конце потока. Это может быть неоптимально. Позаботьтесь о том, чтобы автоспуск часто происходил без явного вмешательства. Например, многие геттеры реализованы таким образом:

return [[myObject retain] autorelease];

поэтому всякий раз, когда вы вызываете метод получения, вы добавляете объект в пул автозапуска.

person IlDan    schedule 18.07.2009
comment
Autorelease заставляет пул autorelease управлять объектом. Это неверно. Пул автозапуска - это не что иное, как средство отложенного обмена сообщениями. Пул автозапуска не берет на себя ответственность за объект, он только перечисляет его для получения сообщения -release в будущем. - person NSResponder; 16.10.2009
comment
Говоря о взятии на себя ответственности, я имел в виду именно это. Я родился в ЕС, но не в Великобритании, по моей вине. - person IlDan; 16.10.2009

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

http://macdevcenter.com/pub/a/mac/2001/07/27/cocoa.html?page=last&x-showcontent=text

person Daniel Earwicker    schedule 18.07.2009

Вы alloc обработали объект, и ваша задача в какой-то момент освободить его. Оба фрагмента кода работают одинаково и правильно, причем способ autorelease является потенциально более медленным аналогом.

Лично я предпочитаю autorelease способ, так как его проще набирать и он почти никогда не является узким местом.

person arul    schedule 18.07.2009

Они оба в порядке. Некоторые люди скажут вам избегать автоспуска из-за "накладных расходов" или чего-то подобного, но правда в том, что накладных расходов практически нет. Идите вперед и сравните его и попытайтесь найти "накладные расходы". Единственная причина, по которой вы бы этого не сделали, - это ситуация с нехваткой памяти, как на iPhone. В OS X у вас практически неограниченная память, так что особой разницы не будет. Просто используйте то, что вам удобнее.

person Chuck    schedule 18.07.2009
comment
Это не так банально. Если объекты автоматически выпускаются в цикле, возможно, что большое количество дополнительных объектов останется, пока не будет опустошен включающий пул автозапуска. Иногда это может привести к значительным накладным расходам памяти и замедлению работы. (Да, это случилось со мной.) Однако это не так уж часто. Обычно вы можете игнорировать проблему, пока она не произойдет, а затем посмотреть, что происходит в цикле, и оптимизировать его, используя явные релизы. - person Jens Alfke; 19.07.2009
comment
Все, что требуется, - это специальное управление пулом автозапуска. Это еще не значит, что автоспуск - это плохо, особенно в общем случае. - person Chuck; 19.07.2009

Я предпочитаю A (автоспуск) из соображений краткости и «безопасности», как это называет Джон. Это упрощает мой код, и у меня никогда не возникало проблем с ним.

То есть до сегодняшнего дня: у меня была проблема с автоматическим освобождением блока перед добавлением его в массив. См. Мой вопрос о stackoverflow: [myArray addObject: [[objcBlock copy] autorelease]] вылетает при освобождении массива (Обновление: оказалось, что проблема была в другом месте моего кода, но все же была небольшая разница в поведении с autorelease…)

person Yang Meyer    schedule 02.05.2011