Понимание подсчета ссылок с помощью Какао и Objective-C

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

Как работают release, retain и autorelease и каковы правила их использования?

(Или, если это не удастся, что вы прочитали, что помогло вам это получить?)

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
122
0
34 614
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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 для сборки с включенной сборкой мусора, что позволяет полностью игнорировать сохранение / освобождение / автозапуск.

Это просто неправильно. Нет необходимости отправлять релиз someObject или autorlease ни в одном из показанных примеров.

mmalc 19.11.2008 12:35

Джошуа (# 6591) - Сборщик мусора в Mac OS X 10.5 кажется довольно крутым, но недоступен для iPhone (или если вы хотите, чтобы ваше приложение работало в версиях Mac OS X до 10.5).

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

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

mmalc 20.10.2008 04:43

Ответ 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];
}

Я понимаю, что все это немного сбивает с толку, но в какой-то момент это щелкнет. Вот несколько справочных материалов, которые помогут вам начать работу:

  • Введение Apple к управлению памятью.
  • Программирование какао для Mac OS X (4-е издание), Аарон Хиллегас - очень хорошо написанная книга с множеством отличных примеров. Это читается как учебник.
  • Если вы действительно погружаетесь, вы можете отправиться в Ранчо Big Nerd. Это учебный центр, которым управляет Аарон Хиллегас - автор упомянутой выше книги. Несколько лет назад я посетил там курс «Введение в какао», и это был отличный способ научиться.

Вы писали: «Вызывая autorelease, мы временно увеличиваем счетчик ссылок». Я считаю, что это неправильно; autorelease только отмечает объект, который будет выпущен в будущем, он не увеличивает счетчик ссылок: cocoadev.com/index.pl?AutoRelease

LKM 13.10.2008 13:47

«Теперь о автоматическом выпуске. Автоспуск используется как удобный (а иногда и необходимый) способ сообщить системе, что нужно освободить этот объект через некоторое время». В качестве вводного предложения это неверно. Он не говорит системе «освободить [его]», он говорит ей уменьшить счетчик удержания.

mmalc 20.10.2008 04:35

Точно так же ваш последний пример кода выпускает NSString слишком много раз - один раз в пуле NSAutorelease через autorelease и напрямую через release :(

rpetrich 08.12.2008 23:47

Вы абсолютно правы, спасибо. Я не уверен, как я пропустил это с моим последним редактированием.

Matt Dillard 09.12.2008 21:49

Не метод экземпляра инициализации увеличивает счетчик удержания, а метод класса alloc. Ссылки на правила Apple приведены ниже, но краткое изложение таково: «Если вы выделяете, сохраняете или копируете его, ваша задача - выпустить его. В противном случае - нет». - stepwise.com/Articles/Technical/2001-03-11.01.html Документы Apple: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm‌ t /…

Michael Baltaks 15.05.2009 03:21

Ваш пример не удачно выбран. В Core Foundation объекты, созданные с помощью метода CFClassCreateInstance, имеют счетчик удержаний, равный 1, который должен быть выпущен вызывающим кодом.

Georg Schölly 19.11.2009 08:20

Большое спасибо за хорошее объяснение. Только одно пока неясно. Если NSString* s = [[NSString alloc] initWithString:@"Hello World"]; возвращает автоматически выпущенный объект (как вы его пишете), почему мне нужно сделать return [s autorelease]; и снова установить его "autorelease", а не только return s?

znq 05.05.2010 13:46

@Stefan: [[NSString alloc] initWithString:@"Hello World"] НЕ возвращает автоматически выпущенный объект. Каждый раз, когда вызывается alloc, счетчик ссылок устанавливается на 1, и ответственность за его освобождение лежит на этом коде. С другой стороны, вызов [NSString stringWithString:]делает возвращает автоматически выпущенный объект.

Matt Dillard 05.05.2010 23:57

Извини, это моя вина. Вы совершенно правы. Каким-то образом я прочитал stringWithString из приведенного выше примера и не посмотрел достаточно внимательно, чтобы заметить, что это initWithString.

znq 06.05.2010 19:43

Интересный факт: поскольку в ответе используются @ "" и NSString, строки остаются постоянными, и, таким образом, абсолютный счетчик сохраненных данных будет как постоянным, так и совершенно нерелевантным .... ни в коем случае не делает ответ неправильным, просто подтверждает тот факт, что абсолютный счетчик удержания никогда не стоит беспокоиться.

bbum 12.07.2010 03:37

Matt Dillard wrote:

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, но, к сожалению, редко разъясняются ясно для новичков.

  1. Если функция, возвращающая объект, имеет в своем имени alloc, create или copy, то объект ваш. Вы должны вызвать в [object release], когда закончите с этим. Или CFRelease(object), если это объект Core-Foundation.

  2. Если в названии НЕТ одного из этих слов, значит, объект принадлежит кому-то другому. Вы должны вызвать [object retain], если хотите сохранить объект после завершения вашей функции.

Вам будет полезно следовать этому соглашению в функциях, которые вы создаете сами.

(Nitpickers: Да, к сожалению, есть несколько вызовов API, которые являются исключениями из этих правил, но они редки).

Это неполно и неточно. Я по-прежнему не понимаю, почему люди пытаются повторять правила, а не просто указывают на соответствующую документацию: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm‌ t /…

mmalc 20.10.2008 20:01

В частности, правила Core Foundation отличаются от правил Какао; см. developer.apple.com/documentation/CoreFoundation/Conceptual/‌…

mmalc 20.10.2008 20:03

Я тоже не согласен. Если функция возвращает что-то, чем она не хочет владеть, она должна автоматически выпустить это. Это вызывающая функция задания функций, которая сохраняет его (при желании). Он не должен иметь НИЧЕГО общего с именем любого вызываемого метода. Это больше похоже на кодирование в стиле C, где владение объектами неясно.

Sam 16.03.2011 22:17

Извини! Думаю, я поспешил проголосовать против. Правила управления памятью Ваш ответ почти цитирует яблочный документ.

Sam 16.03.2011 22:27

Также много хорошей информации о cocoadev:

Как всегда, когда люди начинают пытаться перефразировать справочный материал, они почти всегда делают что-то не так или дают неполное описание.

Apple предоставляет полное описание системы управления памятью Какао в Руководство по программированию управления памятью для какао, в конце которого есть краткое, но точное описание Правила управления памятью.

Попробуйте: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm‌ t /…

Michael Baltaks 13.05.2009 08:02

И для сводных правил: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgm‌ t /…

Michael Baltaks 13.05.2009 08:04

На самом деле это намного лучше одностраничное резюме: developer.apple.com/mac/library/documentation/Cocoa/Conceptu‌ al /…

Brian Moeskau 06.02.2010 05:50

Приведенные выше ответы четко повторяют то, что говорится в документации; проблема, с которой сталкивается большинство новичков, - это недокументированные случаи. Например:

  • Автовыпуск: документы говорят, что это вызовет выпуск «в какой-то момент в будущем». КОГДА?! По сути, вы можете рассчитывать на присутствие объекта до тех пор, пока не выйдете из кода обратно в цикл системных событий. Система МОЖЕТ освободить объект в любое время после текущего цикла событий. (Я думаю, что Мэтт сказал это раньше.)

  • Статические строки: 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 »)». Например, когда зависит от стека пула автозапуска ...

mmalc 02.12.2008 06:58

... «Система МОЖЕТ освободить объект в любое время после текущего цикла событий». Это делает звучание системы менее детерминированным, чем оно есть на самом деле ...

mmalc 02.12.2008 06:58

... NSString foo = [self getBar]; // по-прежнему нет необходимости сохранять или освобождать Это неверно. Тот, кто вызывает getBar, не знает деталей реализации, поэтому * должен сохранить / освободить (обычно через аксессоры), если они хотят использовать его за пределами текущей области.

mmalc 02.12.2008 07:00

Статья «Очень простые правила управления памятью в какао» в некоторых отношениях устарела - в частности, «Объекты, созданные с использованием удобных конструкторов (например, stringWithString NSString), считаются автоматически выпущенными». неправильно - он просто «не принадлежит получателю».

mmalc 02.12.2008 07:01

В iDeveloperTV Network доступен бесплатный скринкаст.

Управление памятью в Objective-C

К сожалению, эта ссылка теперь 404.

Ari Braginsky 28.02.2009 18:35

Я не буду вдаваться в подробности сохранения / выпуска, кроме того, что вы, возможно, захотите подумать о том, чтобы сбросить 50 долларов и получить книгу Hillegass, но я настоятельно рекомендую начать использовать инструменты инструментов на самых ранних этапах разработки вашего приложения (даже первый!). Для этого выполните Run-> Start with performance tools. Я бы начал с Leaks, который является лишь одним из многих доступных инструментов, но он поможет вам показать, когда вы забыли выпустить. Количество информации, которую вам представят, уже не пугает. Но посмотрите этот учебник, чтобы быстро вставать и двигаться:
РУКОВОДСТВО ПО КАКАО: УСТРАНЕНИЕ УТЕЧКИ ПАМЯТИ С ПОМОЩЬЮ ИНСТРУМЕНТОВ

На самом деле попытка утечки сила может быть лучшим способом, в свою очередь, научиться их предотвращать! Удачи ;)

Моя обычная коллекция статей об управлении памятью в Какао:

управление памятью какао

Как уже упоминали несколько человек, Введение в управление памятью от Apple - безусловно, лучшее место для начала.

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

Другие вопросы по теме