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

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

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

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

Ответов (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 Ranch . Это учебный центр, которым управляет Аарон Хиллегас - автор упомянутой выше книги. Несколько лет назад я посетил там курс «Введение в какао», и это был отличный способ научиться.

Джошуа (# 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] .

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

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

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

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

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

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

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

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

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

return [[s autorelease] release];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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