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

122

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

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

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

Мэтт Шеппард
источник

Ответы:

148

Начнем с 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 Ranch . Это учебный центр, которым управляет Аарон Хиллегас - автор упомянутой выше книги. Несколько лет назад я посетил там курс «Введение в какао», и это был отличный способ научиться.
Мэтт Диллард
источник
8
Вы писали: «Вызывая autorelease, мы временно увеличиваем количество ссылок». Я считаю, что это неправильно; autorelease отмечает только объект, который будет выпущен в будущем, он не увеличивает количество ссылок: cocoadev.com/index.pl?AutoRelease
LKM
2
«Теперь о автоматическом выпуске. Автоспуск используется как удобный (а иногда и необходимый) способ сообщить системе, что нужно освободить этот объект через некоторое время». В качестве вводного предложения это неверно. Он не говорит системе «освободить [его]», он говорит ей уменьшить счетчик удержания.
mmalc
3
Большое спасибо за хорошее объяснение. Только одно пока неясно. Если NSString* s = [[NSString alloc] initWithString:@"Hello World"];возвращает автоматически выпущенный объект (как вы его пишете), почему я должен делать return [s autorelease];и снова устанавливать его "autorelease", а не просто return s?
znq 05
3
@Stefan: [[NSString alloc] initWithString:@"Hello World"]НЕ будет возвращать автоматически выпущенный объект. Каждый раз, когда allocвызывается, счетчик ссылок устанавливается на 1, и ответственность за его освобождение лежит на этом коде. [NSString stringWithString:]Вызов, с другой стороны, это возвращает autoreleased объект.
Мэтт Диллард
6
Интересный факт: поскольку в ответе используются @ "" и NSString, строки остаются постоянными и, таким образом, абсолютный счетчик сохраненных данных будет как постоянным, так и совершенно нерелевантным ... ни в коем случае не делает ответ неправильным, просто подтверждает тот факт, что абсолютное удержание никогда не должно вызывать беспокойства.
bbum
10

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

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

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

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

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

Эндрю Грант
источник
11
Это неполно и неточно. Я по-прежнему не понимаю, почему люди пытаются повторять правила, а не просто указывают на соответствующую документацию: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc
4
В частности, правила Core Foundation отличаются от правил Какао; см. developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc,
1
Я тоже не согласен. Если функция возвращает что-то, чем она не хочет владеть, она должна автоматически выпустить это. Это вызывающий объект функции, который сохраняет его (при желании). Это не должно иметь НИЧЕГО общего с именем любого вызываемого метода. Это больше похоже на кодирование в стиле C, где право собственности на объекты неясно.
Сэм
1
Сожалею! Думаю, я поспешил проголосовать против. Правила управления памятью Ваш ответ почти цитирует яблочный документ.
Сэм
8

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

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

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

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

Крис Хэнсон
источник
6

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

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

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

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

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

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

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

mmalc
источник
Попробуйте: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
Майкл Балтакс,
1
И для сводных правил: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
Майкл Балтакс,
2
На самом деле это гораздо лучшее одностраничное резюме: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Брайан Моэскау,
6

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

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

обкрадывать
источник
5

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

return [[s autorelease] release];

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

NilObject
источник
4

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

Майк Макмастер
источник
2

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

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

  • Статические строки : 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 для этого объекта.

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

Третье должно быть самоочевидным.

Olie
источник
«Autorelease: документы говорят, что это вызовет релиз» в какой-то момент в будущем. «КОГДА ?!» В документации четко сказано по этому поводу: «autorelease просто означает« отправить сообщение о выпуске позже »(для некоторого определения позже - см.« Autorelease Pools »)». В частности, когда зависит от стека пула автозапуска ...
mmalc
... «Система МОЖЕТ освободить объект в любое время после текущего цикла событий». Это делает звучание системы менее детерминированным, чем оно есть на
самом деле
... NSString foo = [self getBar]; // по-прежнему нет необходимости сохранять или освобождать Это неверно. Тот, кто вызывает getBar, не знает деталей реализации, поэтому * следует сохранить / выпустить (обычно через аксессоры), если они хотят использовать его за пределами текущей области.
mmalc
Статья «Очень простые правила управления памятью в какао» в некоторых отношениях устарела - в частности, «Объекты, созданные с помощью удобных конструкторов (например, stringWithString NSString), считаются автоматически выпущенными». неправильно - он просто «не принадлежит получателю».
mmalc 02
0

Как уже упоминали несколько человек, лучше всего начать с Apple Intro to Memory Management .

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

Брайан Моэскау
источник