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

StackOverflow https://stackoverflow.com/questions/6578

Вопрос

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

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

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

Это было полезно?

Решение

Давайте начнем с retain и release; autorelease на самом деле это просто частный случай, как только вы поймете основные концепции.

В Cocoa каждый объект отслеживает, сколько раз на него ссылаются (в частности, 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 к управлению памятью.
  • Программирование Cocoa для Mac OS X (4-е издание), автор Аарон Хиллегас - очень хорошо написанная книга с множеством замечательных примеров.Это читается как учебное пособие.
  • Если вы действительно ныряете с головой, вы могли бы отправиться в Большое Ранчо Ботаников.Это учебное заведение, которым руководит Аарон Хиллегас - автор упомянутой выше книги.Несколько лет назад я посещал там курс "Введение в какао", и это был отличный способ научиться.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

вернуть [[авторелизный] релиз];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ...

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

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

Вот фрагмент из Очень Простые Правила Управления Памятью В Cocoa:

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

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

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

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

3-й должен быть понятен сам по себе.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top