Я только начинаю изучать Objective-C и Cocoa, чтобы поиграть с iPhone SDK. Мне достаточно комфортно с концепцией C malloc и free, но схема подсчета ссылок Какао меня довольно смущает. Мне сказали, что это очень элегантно, как только вы это поймете, но я еще не закончил.
Как работают release, retain и autorelease и каковы правила их использования?
(Или, если это не удастся, что вы прочитали, что помогло вам это получить?)





Objective-C использует Подсчет ссылок, что означает, что каждый объект имеет счетчик ссылок. Когда объект создается, он имеет счетчик ссылок «1». Проще говоря, когда на объект ссылаются (то есть где-то хранится), он «сохраняется», что означает, что его счетчик ссылок увеличивается на единицу. Когда объект больше не нужен, он «освобождается», что означает, что его счетчик ссылок уменьшается на единицу.
Когда счетчик ссылок на объект равен 0, объект освобождается. Это базовый подсчет ссылок.
Для некоторых языков количество ссылок автоматически увеличивается и уменьшается, но objective-c не является одним из этих языков. Таким образом, программист несет ответственность за сохранение и выпуск.
Типичный способ написания метода:
id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;
Проблема необходимости не забывать освобождать любые полученные ресурсы внутри кода утомительна и подвержена ошибкам. Objective-C вводит еще одну концепцию, направленную на то, чтобы сделать это намного проще: пулы автоматического выпуска. Пулы автозапуска - это специальные объекты, которые устанавливаются в каждом потоке. Это довольно простой класс, если вы посмотрите NSAutoreleasePool.
Когда объект получает сообщение "автозапуск", отправленное ему, объект будет искать любые пулы автозапуска, находящиеся в стеке для этого текущего потока. Он добавит объект в список как объект, которому будет отправлено сообщение о «освобождении» в какой-то момент в будущем, что обычно происходит при освобождении самого пула.
Взяв приведенный выше код, вы можете переписать его, чтобы он был короче и удобнее для чтения, сказав:
id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;
Поскольку объект автоматически освобождается, нам больше не нужно явно вызывать для него «освобождение». Это потому, что мы знаем, что какой-то пул автозапуска сделает это за нас позже.
Надеюсь, это поможет. Статья в Википедии довольно хороша о подсчете ссылок. Подробнее о пулы с автоматическим выпуском можно найти здесь. Также обратите внимание, что если вы создаете для Mac OS X 10.5 и более поздних версий, вы можете указать Xcode для сборки с включенной сборкой мусора, что позволяет полностью игнорировать сохранение / освобождение / автозапуск.
Джошуа (# 6591) - Сборщик мусора в Mac OS X 10.5 кажется довольно крутым, но недоступен для iPhone (или если вы хотите, чтобы ваше приложение работало в версиях Mac OS X до 10.5).
Кроме того, если вы пишете библиотеку или что-то, что может быть повторно использовано, использование режима GC блокирует любого, кто использует код, чтобы он также использовал режим GC, поэтому, насколько я понимаю, любой, кто пытается написать широко повторно используемый код, имеет тенденцию идти на управление память вручную.
Вполне возможно написать гибридную структуру, которая поддерживает как сборщик мусора, так и подсчет ссылок.
Ответ NilObject - хорошее начало. Вот некоторая дополнительная информация, относящаяся к ручному управлению памятью (требуется на iPhone).
Если вы лично alloc/init объект, он идет со счетчиком ссылок 1. Вы несете ответственность за очистку после него, когда он больше не нужен, позвонив в [foo release] или [foo autorelease]. release очищает его сразу же, тогда как autorelease добавляет объект в пул autorelease, который автоматически освобождает его позже.
autorelease в первую очередь используется, когда у вас есть метод, который должен вернуть рассматриваемый объект (поэтому вы не можете вручную освободить его, иначе вы вернете объект nil), но вы также не хотите его удерживать.
Если вы приобрели объект, для которого не вызывали alloc / init, например:
foo = [NSString stringWithString:@"hello"];
но вы хотите сохранить этот объект, вам нужно вызвать [foo keep]. В противном случае возможно, что он получит autoreleased, и вы будете удерживать нулевую ссылку (как в приведенном выше примере stringWithString). Когда он вам больше не понадобится, позвоните в [foo release].
Начнем с retain и release; autorelease - это действительно особый случай, если вы понимаете основные концепции.
В Какао каждый объект отслеживает, сколько раз на него ссылаются (в частности, базовый класс NSObject реализует это). Вызывая retain для объекта, вы сообщаете ему, что хотите увеличить его счетчик ссылок на единицу. Вызывая release, вы сообщаете объекту, что отпускаете его, и его счетчик ссылок уменьшается. Если после вызова release счетчик ссылок теперь равен нулю, то память этого объекта освобождается системой.
Основное отличие этого от malloc и free заключается в том, что любому данному объекту не нужно беспокоиться о сбое других частей системы, потому что вы освободили память, которую они использовали. Предполагая, что все подыгрывают и сохраняют / освобождают в соответствии с правилами, когда один фрагмент кода сохраняет, а затем освобождает объект, любой другой фрагмент кода, также ссылающийся на объект, не будет затронут.
Что иногда может сбивать с толку, так это знание обстоятельств, при которых вам следует звонить retain и release. Мое общее эмпирическое правило состоит в том, что если я хочу держаться за объект в течение некоторого времени (например, если это переменная-член в классе), то мне нужно убедиться, что счетчик ссылок объекта знает обо мне. Как описано выше, счетчик ссылок на объект увеличивается путем вызова retain. По соглашению, он также увеличивается (на самом деле устанавливается в 1), когда объект создается с помощью метода "init". В любом из этих случаев я обязан вызвать release для объекта, когда я закончу с ним. Если я этого не сделаю, произойдет утечка памяти.
Пример создания объекта:
NSString* s = [[NSString alloc] init]; // Ref count is 1
[s retain]; // Ref count is 2 - silly
// to do this after init
[s release]; // Ref count is back to 1
[s release]; // Ref count is 0, object is freed
Теперь о autorelease. Автозапуск используется как удобный (а иногда и необходимый) способ сообщить системе, что нужно освободить этот объект через некоторое время. С точки зрения водопровода, когда вызывается autorelease, NSAutoreleasePool текущего потока получает предупреждение о вызове. NSAutoreleasePool теперь знает, что как только у него появится возможность (после текущей итерации цикла событий), он может вызвать release для объекта. С нашей точки зрения, как программисты, он заботится о вызове release за нас, поэтому нам не нужно (да и не следует).
Важно отметить, что (опять же, по соглашению) все методы создания объектов учебный класс возвращают автоматически выпущенный объект. Например, в следующем примере переменная s имеет счетчик ссылок 1, но после завершения цикла событий она будет уничтожена.
NSString* s = [NSString stringWithString:@"Hello World"];
Если вы хотите закрепить эту строку, вам нужно будет явно вызвать retain, а затем явно release, когда вы закончите.
Рассмотрим следующий (очень надуманный) фрагмент кода, и вы увидите ситуацию, в которой требуется autorelease:
- (NSString*)createHelloWorldString
{
NSString* s = [[NSString alloc] initWithString:@"Hello World"];
// Now what? We want to return s, but we've upped its reference count.
// The caller shouldn't be responsible for releasing it, since we're the
// ones that created it. If we call release, however, the reference
// count will hit zero and bad memory will be returned to the caller.
// The answer is to call autorelease before returning the string. By
// explicitly calling autorelease, we pass the responsibility for
// releasing the string on to the thread's NSAutoreleasePool, which will
// happen at some later time. The consequence is that the returned string
// will still be valid for the caller of this function.
return [s autorelease];
}
Я понимаю, что все это немного сбивает с толку, но в какой-то момент это щелкнет. Вот несколько справочных материалов, которые помогут вам начать работу:
Вы писали: «Вызывая autorelease, мы временно увеличиваем счетчик ссылок». Я считаю, что это неправильно; autorelease только отмечает объект, который будет выпущен в будущем, он не увеличивает счетчик ссылок: cocoadev.com/index.pl?AutoRelease
«Теперь о автоматическом выпуске. Автоспуск используется как удобный (а иногда и необходимый) способ сообщить системе, что нужно освободить этот объект через некоторое время». В качестве вводного предложения это неверно. Он не говорит системе «освободить [его]», он говорит ей уменьшить счетчик удержания.
Точно так же ваш последний пример кода выпускает NSString слишком много раз - один раз в пуле NSAutorelease через autorelease и напрямую через release :(
Вы абсолютно правы, спасибо. Я не уверен, как я пропустил это с моим последним редактированием.
Не метод экземпляра инициализации увеличивает счетчик удержания, а метод класса alloc. Ссылки на правила Apple приведены ниже, но краткое изложение таково: «Если вы выделяете, сохраняете или копируете его, ваша задача - выпустить его. В противном случае - нет». - stepwise.com/Articles/Technical/2001-03-11.01.html Документы Apple: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm t /…
Ваш пример не удачно выбран. В Core Foundation объекты, созданные с помощью метода CFClassCreateInstance, имеют счетчик удержаний, равный 1, который должен быть выпущен вызывающим кодом.
Большое спасибо за хорошее объяснение. Только одно пока неясно. Если NSString* s = [[NSString alloc] initWithString:@"Hello World"]; возвращает автоматически выпущенный объект (как вы его пишете), почему мне нужно сделать return [s autorelease]; и снова установить его "autorelease", а не только return s?
@Stefan: [[NSString alloc] initWithString:@"Hello World"] НЕ возвращает автоматически выпущенный объект. Каждый раз, когда вызывается alloc, счетчик ссылок устанавливается на 1, и ответственность за его освобождение лежит на этом коде. С другой стороны, вызов [NSString stringWithString:]делает возвращает автоматически выпущенный объект.
Извини, это моя вина. Вы совершенно правы. Каким-то образом я прочитал stringWithString из приведенного выше примера и не посмотрел достаточно внимательно, чтобы заметить, что это initWithString.
Интересный факт: поскольку в ответе используются @ "" и NSString, строки остаются постоянными, и, таким образом, абсолютный счетчик сохраненных данных будет как постоянным, так и совершенно нерелевантным .... ни в коем случае не делает ответ неправильным, просто подтверждает тот факт, что абсолютный счетчик удержания никогда не стоит беспокоиться.
return [[s autorelease] release];
При автоматическом выпуске нет сохраняет объект. Autorelease просто помещает его в очередь, чтобы выпустить позже. Вы не хотите, чтобы там было заявление о выпуске.
Если вы пишете код для настольного компьютера и можете ориентироваться на Mac OS X 10.5, вам следует хотя бы изучить возможность использования сборки мусора Objective-C. Это действительно упростит большую часть вашей разработки - поэтому Apple приложила все усилия, чтобы создать его в первую очередь и обеспечить его хорошую производительность.
Что касается правил управления памятью, когда сборщик мусора не используется:
+alloc/+allocWithZone:, +new, -copy или -mutableCopy, или если вы -retain объект, вы становитесь владельцем этого объекта и должны гарантировать, что он будет отправлен -release.-release.-release, вы можете отправить его самостоятельно или отправить объект -autorelease, и текущий пул с автоматическим выпуском отправит его -release (один раз на каждый полученный -autorelease), когда пул будет опустошен.Обычно -autorelease используется как способ гарантировать, что объекты живут в течение всего текущего события, но очищаются впоследствии, поскольку существует пул автозапуска, который окружает обработку событий Какао. В Какао далеко чаще возвращает автоматически выпущенные объекты вызывающей стороне, чем объекты, которые вызывающей стороне необходимо освободить.
Если вы понимаете процесс сохранения / выпуска, то есть два золотых правила, которые очевидны для опытных программистов Cocoa, но, к сожалению, редко разъясняются ясно для новичков.
Если функция, возвращающая объект, имеет в своем имени alloc, create или copy, то объект ваш. Вы должны вызвать в [object release], когда закончите с этим. Или CFRelease(object), если это объект Core-Foundation.
Если в названии НЕТ одного из этих слов, значит, объект принадлежит кому-то другому. Вы должны вызвать [object retain], если хотите сохранить объект после завершения вашей функции.
Вам будет полезно следовать этому соглашению в функциях, которые вы создаете сами.
(Nitpickers: Да, к сожалению, есть несколько вызовов API, которые являются исключениями из этих правил, но они редки).
Это неполно и неточно. Я по-прежнему не понимаю, почему люди пытаются повторять правила, а не просто указывают на соответствующую документацию: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm t /…
В частности, правила Core Foundation отличаются от правил Какао; см. developer.apple.com/documentation/CoreFoundation/Conceptual/…
Я тоже не согласен. Если функция возвращает что-то, чем она не хочет владеть, она должна автоматически выпустить это. Это вызывающая функция задания функций, которая сохраняет его (при желании). Он не должен иметь НИЧЕГО общего с именем любого вызываемого метода. Это больше похоже на кодирование в стиле C, где владение объектами неясно.
Извини! Думаю, я поспешил проголосовать против. Правила управления памятью Ваш ответ почти цитирует яблочный документ.
Также много хорошей информации о cocoadev:
Как всегда, когда люди начинают пытаться перефразировать справочный материал, они почти всегда делают что-то не так или дают неполное описание.
Apple предоставляет полное описание системы управления памятью Какао в Руководство по программированию управления памятью для какао, в конце которого есть краткое, но точное описание Правила управления памятью.
Попробуйте: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm t /…
И для сводных правил: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm t /…
На самом деле это намного лучше одностраничное резюме: developer.apple.com/mac/library/documentation/Cocoa/Conceptu al /…
Приведенные выше ответы четко повторяют то, что говорится в документации; проблема, с которой сталкивается большинство новичков, - это недокументированные случаи. Например:
Автовыпуск: документы говорят, что это вызовет выпуск «в какой-то момент в будущем». КОГДА?! По сути, вы можете рассчитывать на присутствие объекта до тех пор, пока не выйдете из кода обратно в цикл системных событий. Система МОЖЕТ освободить объект в любое время после текущего цикла событий. (Я думаю, что Мэтт сказал это раньше.)
Статические строки: NSString *foo = @"bar"; - вы должны оставить это или выпустить? Нет. Как насчет
-(void)getBar {
return @"bar";
}
...
NSString *foo = [self getBar]; // still no need to retain or release
Правило создания: Если вы его создали, вы являетесь его владельцем, и ожидается, что вы его выпустите.
В общем, новые программисты на Какао запутались, не понимая, какие подпрограммы возвращают объект с retainCount > 0.
Вот отрывок из Очень простые правила управления памятью в какао:
Retention Count rules
- Within a given block, the use of -copy, -alloc and -retain should equal the use of -release and -autorelease.
- Objects created using convenience constructors (e.g. NSString's stringWithString) are considered autoreleased.
- Implement a -dealloc method to release the instancevariables you own
В первом пункте говорится: если вы вызвали alloc (или new fooCopy), вам нужно вызвать release для этого объекта.
Второй пункт говорит: если вы используете удобный конструктор и вам нужен объект, чтобы торчать (как в случае с изображением, которое будет нарисовано позже), вам необходимо сохранить (а затем отпустить) его.
Третье должно быть самоочевидным.
«Autorelease: документы говорят, что это вызовет релиз» в какой-то момент в будущем. «КОГДА ?!» В документации четко сказано по этому поводу: «autorelease просто означает« отправить сообщение о выпуске позже »(для некоторого определения позже - см.« Autorelease Pools »)». Например, когда зависит от стека пула автозапуска ...
... «Система МОЖЕТ освободить объект в любое время после текущего цикла событий». Это делает звучание системы менее детерминированным, чем оно есть на самом деле ...
... NSString foo = [self getBar]; // по-прежнему нет необходимости сохранять или освобождать Это неверно. Тот, кто вызывает getBar, не знает деталей реализации, поэтому * должен сохранить / освободить (обычно через аксессоры), если они хотят использовать его за пределами текущей области.
Статья «Очень простые правила управления памятью в какао» в некоторых отношениях устарела - в частности, «Объекты, созданные с использованием удобных конструкторов (например, stringWithString NSString), считаются автоматически выпущенными». неправильно - он просто «не принадлежит получателю».
В iDeveloperTV Network доступен бесплатный скринкаст.
Управление памятью в Objective-C
К сожалению, эта ссылка теперь 404.
Я не буду вдаваться в подробности сохранения / выпуска, кроме того, что вы, возможно, захотите подумать о том, чтобы сбросить 50 долларов и получить книгу Hillegass, но я настоятельно рекомендую начать использовать инструменты инструментов на самых ранних этапах разработки вашего приложения (даже первый!). Для этого выполните Run-> Start with performance tools. Я бы начал с Leaks, который является лишь одним из многих доступных инструментов, но он поможет вам показать, когда вы забыли выпустить. Количество информации, которую вам представят, уже не пугает. Но посмотрите этот учебник, чтобы быстро вставать и двигаться:
РУКОВОДСТВО ПО КАКАО: УСТРАНЕНИЕ УТЕЧКИ ПАМЯТИ С ПОМОЩЬЮ ИНСТРУМЕНТОВ
На самом деле попытка утечки сила может быть лучшим способом, в свою очередь, научиться их предотвращать! Удачи ;)
Моя обычная коллекция статей об управлении памятью в Какао:
Как уже упоминали несколько человек, Введение в управление памятью от Apple - безусловно, лучшее место для начала.
Одна полезная ссылка, о которой я еще не упоминал, - это Практическое управление памятью. Вы найдете его в середине документации Apple, если прочитаете их, но на него стоит прямая ссылка. Это блестящее резюме правил управления памятью с примерами и типичными ошибками (в основном то, что другие ответы здесь пытаются объяснить, но не так хорошо).
Это просто неправильно. Нет необходимости отправлять релиз someObject или autorlease ни в одном из показанных примеров.