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

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

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

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

9.08.2008 03:13:04
14 ОТВЕТОВ
РЕШЕНИЕ

Давайте начнем с 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 . Это учебный центр, которым руководит Аарон Хиллегас - автор упомянутой выше книги. Я посещал курс «Введение в какао» там несколько лет назад, и это был отличный способ учиться.
148
30.05.2012 20:41:22
Вы писали: «Вызывая autorelease, мы временно увеличиваем количество ссылок». Я думаю, что это неправильно; autorelease помечает только объект, который будет выпущен в будущем, но не увеличивает количество ссылок: cocoadev.com/index.pl?AutoRelease
LKM 13.10.2008 09:47:57
«Теперь для авто-релиза. Авто-релиз используется как удобный (и иногда необходимый) способ сообщить системе, что через некоторое время освободить этот объект». В качестве вводного предложения это неправильно. Это не говорит системе «освободить [это]», это говорит, что это уменьшает счет сохранения.
mmalc 20.10.2008 00:35:44
Большое спасибо за хорошее объяснение. Только одна вещь, которая до сих пор неясно. Если NSString* s = [[NSString alloc] initWithString:@"Hello World"];возвращает автоматически выпущенный объект (как вы его пишете), почему я должен сделать a return [s autorelease];и установить его "autorelease" снова, а не только return s?
znq 5.05.2010 09:46:46
@Stefan: [[NSString alloc] initWithString:@"Hello World"]НЕ возвращает автоматически выпущенный объект. Каждый раз, когда allocвызывается, счетчик ссылок устанавливается в 1, и этот код отвечает за то, чтобы он был освобожден. [NSString stringWithString:]Вызов, с другой стороны, это возвращает autoreleased объект.
Matt Dillard 5.05.2010 19:57:42
Забавные мелочи: так как в ответе используются @ "" и NSString, строки постоянны во всем, и, таким образом, абсолютный счет удержания будет как постоянным, так и совершенно неуместным .... не делает ответ неверным, в любом случае, просто подтверждает тот факт, что абсолютные показатели удержания никогда не являются тем, о чем вам следует беспокоиться.
bbum 11.07.2010 23:37:55

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

Когда счетчик ссылок на объект равен 0, объект освобождается. Это основной подсчет ссылок.

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

Типичный способ написания метода:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Проблема необходимости не забывать высвобождать любые полученные ресурсы внутри кода является утомительной и подверженной ошибкам. Objective-C представляет другую концепцию, нацеленную на то, чтобы сделать это намного проще: пулы Autorelease. Пулы автоматического освобождения - это специальные объекты, которые устанавливаются в каждом потоке. Это довольно простой класс, если вы посмотрите NSAutoreleasePool.

Когда объект получает сообщение «autorelease», отправленное ему, объект будет искать любые пулы autorelease, находящиеся в стеке для этого текущего потока. Он добавит объект в список как объект для отправки сообщения «release» в какой-то момент в будущем, как правило, когда освобождается сам пул.

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

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Поскольку объект автоматически освобожден, нам больше не нужно явно вызывать «release» для него. Это потому, что мы знаем, что какой-то пул авто-релизов сделает это за нас позже.

Надеюсь, это поможет. Статья в Википедии хороша для подсчета ссылок. Более подробную информацию об авто-релизных пулах можно найти здесь . Также обратите внимание, что если вы собираете для Mac OS X 10.5 и более поздних версий, вы можете указать Xcode собирать с включенной сборкой мусора, что позволит вам полностью игнорировать retain / release / autorelease.

6
9.08.2008 03:48:10
Это просто неправильно. Нет необходимости отправлять какой-либо выпуск или автозапуск какого-либо объекта в любом из приведенных примеров.
mmalc 19.11.2008 09:35:59

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

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

6
9.08.2008 03:59:35
Вполне возможно написать гибридную среду, которая поддерживает GC и подсчет ссылок.
mmalc 20.10.2008 00:43:02

Ответ NilObject - хорошее начало. Вот некоторая дополнительная информация, относящаяся к ручному управлению памятью ( требуется на iPhone ).

Если вы лично alloc/initобъект, он имеет счетчик ссылок 1. Вы несете ответственность за очистку после него, когда он больше не нужен, по телефону [foo release]или [foo autorelease]. release очищает его сразу же, тогда как autorelease добавляет объект в пул autorelease, который автоматически освобождает его позднее.

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

Если вы приобрели объект, для которого вы не вызывали alloc / init, например:

foo = [NSString stringWithString:@"hello"];

но вы хотите держаться за этот объект, вам нужно вызвать [foo retain]. В противном случае, возможно, он получит, autoreleasedи вы будете удерживать нулевую ссылку (как это было бы в приведенном выше stringWithStringпримере ). Когда вам это больше не нужно, звоните [foo release].

4
25.03.2016 21:13:23

Мэтт Диллард написал :

возврат [[s autorelease] релиз];

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

5
23.05.2017 12:32:05

Если вы пишете код для настольного компьютера и можете ориентироваться на Mac OS X 10.5, вы должны хотя бы изучить использование сборки мусора в Objective-C. Это действительно упростит большую часть вашей разработки - вот почему Apple прилагает все усилия для того, чтобы создать ее и обеспечить ее высокую производительность.

Что касается правил управления памятью, когда не используется GC:

  • Если вы создаете новый объект , используя +alloc/+allocWithZone:, +new, -copyили -mutableCopyили если вы -retainобъект, вы принимаете права на него и должен убедиться , что он направляется -release.
  • Если вы получаете объект любым другим способом, вы не являетесь его владельцем и не должны гарантировать, что он был отправлен -release.
  • Если вы хотите убедиться, что объект отправлен, -releaseвы можете либо отправить его самостоятельно, либо вы можете отправить объект, -autoreleaseи текущий пул автоматического выпуска отправит его -release(один раз для каждого полученного -autorelease), когда пул будет очищен.

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

8
9.08.2008 22:27:12

Если вы понимаете процесс сохранения / освобождения, то есть два золотых правила, которые очевидны для опытных программистов Какао, но, к сожалению, они редко излагаются ясно для новичков.

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

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

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

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

10
19.12.2016 03:41:31
Это неполно и неточно. Я до сих пор не могу понять, почему люди пытаются повторять правила, а не просто указывают на соответствующую документацию: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc 20.10.2008 16:01:51
Правила основного фонда, в частности, отличаются от правил какао; см. developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc 20.10.2008 16:03:11
Я тоже не согласен. Если функция возвращает что-то, что она не хочет иметь, она должна автоматически высвобождать это. Вызывающая функция должна сохранить ее (при желании). Он не должен иметь НИЧЕГО общего с именем любого вызываемого метода. Это больше кодирование в стиле C, когда владение объектами неясно.
Sam 16.03.2011 19:17:20
Сожалею! Я думаю, что я был поспешным в голосовании с понижением. Правила управления памятью Ваш ответ почти цитирует Apple Doc.
Sam 16.03.2011 19:27:10

Много полезной информации о cocoadev тоже:

1
15.09.2008 20:46:23

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

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

6
20.10.2008 00:42:12
Michael Baltaks 13.05.2009 04:02:40
Michael Baltaks 13.05.2009 04:04:02
На самом деле, это намного лучше, одностраничное резюме: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau 6.02.2010 02:50:31

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

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

  • Статические строки : NSString *foo = @"bar";- вы должны сохранить или освободить это? Нет. Как насчет

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • Правило создания : если вы создали его, вы являетесь его владельцем и ожидаете его выпуска.

В общем случае, новые программисты Какао путаются, не понимая, какие процедуры возвращают объект с retainCount > 0.

Вот фрагмент из очень простых правил управления памятью в какао :

Правила подсчета удержания

  • В пределах данного блока использование -copy, -alloc и -retain должно равняться использованию -release и -autorelease.
  • Объекты, созданные с помощью вспомогательных конструкторов (например, stringWithString NSString), считаются автоматически выпущенными.
  • Реализуйте метод -dealloc для освобождения ваших переменных экземпляра

В первом пуле говорится: если вы звонили alloc(или new fooCopy), вам нужно вызвать release для этого объекта.

Во 2-м пункте говорится: если вы используете удобный конструктор и вам нужно, чтобы объект висел вокруг (как с изображением, которое будет нарисовано позже), вам нужно сохранить (а затем отпустить) его.

Третье должно быть само за себя.

2
1.11.2011 17:01:06
«Autorelease: документы говорят, что это вызовет релиз» в какой-то момент в будущем. «КОГДА ?!» Документы на этот счет ясны: «autorelease просто означает« отправить сообщение о выпуске позже »(некоторое определение позже - см.« Пулы Autorelease »).» В крайнем случае, когда зависит от стека пула автоматического выпуска ...
mmalc 2.12.2008 03:58:02
... "Система МОЖЕТ освободить объект в любое время после текущего цикла событий." Это делает звучание системы менее детерминированным, чем есть ...
mmalc 2.12.2008 03:58:45
... NSString foo = [self getBar]; // все еще нет необходимости сохранять или освобождать Это неправильно. Тот, кто вызывает getBar, не знает деталей реализации, поэтому * должен сохранить / освободить (обычно через средства доступа), если он хочет использовать его за пределами текущей области.
mmalc 2.12.2008 04:00:32
Статья «Очень простые правила для управления памятью в какао» в нескольких отношениях устарела, в частности «Объекты, созданные с помощью удобных конструкторов (например, stringWithString NSString), считаются автоматически выпущенными». не прав - он просто "не принадлежит получателю".
mmalc 2.12.2008 04:01:20

В сети iDeveloperTV есть бесплатный скринкаст

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

4
17.06.2011 06:54:52
К сожалению, эта ссылка теперь 404.
Ari Braginsky 28.02.2009 15:35:59

Я не буду вдаваться в подробности сохранения / выпуска, за исключением того, что вы можете подумать о том, чтобы сбросить 50 долларов и получить книгу Hillegass, но я настоятельно рекомендую начать использовать инструменты Instruments на самых ранних этапах разработки вашего приложения (даже вашего первый!). Для этого выполните Run-> Start с инструментами производительности. Я бы начал с Leaks, который является лишь одним из многих доступных инструментов, но поможет показать вам, когда вы забыли выпустить. Это просто пугает, сколько информации вы будете представлены. Но ознакомьтесь с этим руководством, чтобы быстро приступить к работе: Учебное пособие по
какао: исправление воспоминаний с инструментами

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

6
27.01.2013 13:24:21

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

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

5
18.06.2013 11:32:49

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

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

0
6.02.2010 03:01:11