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

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

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

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

15 ответов

Решение

Давайте начнем с 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-е издание) Аарона Хиллегаса - очень хорошо написанная книга с множеством замечательных примеров. Это читается как учебник.
  • Если вы действительно погружаетесь, вы можете отправиться на ранчо Большого ботаника. Это учебный центр, которым руководит Аарон Хиллегас - автор упомянутой выше книги. Я посещал курс "Введение в какао" там несколько лет назад, и это был отличный способ учиться.

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

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

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

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

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

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

вернуть [[s autorelease] release];

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

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

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

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

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

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

  • Статические строки: 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-м пункте говорится: если вы используете удобный конструктор и вам нужно, чтобы объект висел вокруг (как с изображением, которое будет нарисовано позже), вам нужно сохранить (а затем отпустить) его.

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

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

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

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

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

механизм подсчета ссылок — это реализацияobject ownershipтехника управления памятью

MRC, ARC, GC

Manual Reference Counting(MRC)илиManual Retain-Release(MRR)как разработчик вы несете ответственность за подсчет ссылок на объекты вручную

Automatic Reference Counting(ARC)был представлен в iOS v5.0 и OS X Mountain Lion с xCode v4.2.

Garbage Collection(GC)был доступен для Mac OS и устарел в OS X Mountain Lion. Должен переехать в ARC

Подсчет ссылок в MRC и ARC

      //MRC
NSLog(@"Retain Count: %d", [variable retainCount]);

//ARC
NSLog(@"Retain Count: %ld", CFGetRetainCount((__bridge CFTypeRef) variable));

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

Жизненный цикл:

  • Выделение объекта
  • Работа со счетчиком ссылок
  • Освобождение объекта. вызывается, когдаretainCount == 0

MRC

      A *a1 = [[A alloc] init]; //A has retainCount = 1
    
A *a2 = a1;
[a2 retain]; //A has retainCount = 2

// a1, a2 -> reference on the same object in heap with retainCount = 2

Правильный способ освобождения объекта:

  1. . Если использовать только этот прием - висящий указатель. Потому что он по-прежнему может указывать на объект в куче и отправлять сообщение. Например, A делает +1, B просто делает ссылку (без +1), A делает выпуск -1, счетчик == 0, объект освобождается, но B имеет ссылку на кучу
  2. = nil. Если использовать только эту технику - утечка памяти.deinitне будет вызван. Например, когда счетчик ==1 и вы делаете= nil. В любом случае, когда вы забываете освободить неиспользуемый объект, где счетчик! = 0, это приводит к утечке памяти.
      A *a = [[A alloc] init]; //++retainCount = 1
[a release]; //--retainCount = 0
a = nil; //guarantees that even somebody else has a reference to the object, and we try to send some message thought variable `a` this message will be just skipped

Работа со счетчиком ссылок (правила владельца объекта):

  • (0 -> 1)alloc,new,copy,mutableCopy
  • (+1) Вы можете владеть объектом столько раз, сколько вам нужно (вы можете звонить несколько раз)
  • (-1) Если вы владелец, вы должны выпустить его. Если вы отпустите больше, чем continueCount, это будет 0
  • (-1) Добавляет объект, который следует освободить, вautorelease pool. Этот пул будет обработан в конце итерационного цикла RunLoop(то есть, когда все задачи будут завершены в стеке) [About] и после этого будет применен ко всем объектам в пуле.
  • (-1) Принудительно обрабатывает пул автовыпуска (объекты выпуска) в конце блока. Он используется, когда вы работаете в цикле и хотите как можно скорее очистить ресурсы. Если вы этого не сделаете, объем вашей памяти будет постоянно увеличиваться.

Например, это полезно при вызове метода, когда вы выделяете там новый объект и возвращаете его.

      - (B *)foo {
    B *b1 = [[B alloc] init]; //retainCount = 1

    //correct way 
    [b1 autorelease];

    //wrong way is to return b1 without putting it into autorelease beforehand
    return b1; 
}

- (void)testFoo {
    B *b2 = [a foo];
    //if autorelease was not used in foo
    [b2 retain]; //retainCount = 2
    //some logic
    [b2 release]; //retainCount = 1
    
    //Memory Leak
}

@autoreleasepoolпример:

      - (void)testFoo {
    @autoreleasepool {
        for(i=0; i<100; i++) {
            B *b2 = [a foo];
            //process b2
        }
    }
}

АРК

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

Включить/отключить ARC

      //enable
-fobjc-arc
//disable
-fno-objc-arc

Варианты от большего к меньшему приоритету

      //1. local file - most priority
Build Phases -> Compile Sources -> Compiler Flags(Select files -> Enter) 

//2. global
Build Settings -> Other C Flags(OTHER_CFLAGS)

//3. global
Build Settings -> Objective-C Automatic Reference Counting(CLANG_ENABLE_OBJC_ARC)

Проверьте, включен/отключен ли ARC

Preprocessor __has_featureиспользуется функция

      __has_feature(objc_arc)

Время компиляции

      // error if ARC is Off. Force to enable ARC
#if  ! __has_feature(objc_arc)
    #error Please enable ARC for this file
#endif

//or

// error if ARC is On. Force to disable ARC
#if  __has_feature(objc_arc)
    #error Please disable ARC for this file
#endif

Время выполнения

      #if __has_feature(objc_arc)
    // ARC is On
    NSLog(@"ARC on");
#else
    // ARC is Off
    NSLog(@"ARC off");
#endif

Реверс-инжиниринг (для Objective-C)

      //ARC is enabled
otool -I -v <binary_path> | grep "<mrc_message>"
//e.g.
otool -I -v "/Users/alex/ARC_experiments.app/ARC_experiments"  | grep "_objc_release"

//result
0x00000001000080e0   748 _objc_release

//<mrc_message>
_objc_retain
_objc_release
_objc_autoreleaseReturnValue
_objc_retainAutoreleaseReturnValue
_objc_retainAutoreleasedReturnValue
_objc_storeStrong

Инструмент для миграции Objective-C MRC в ARC

ARC генерирует ошибки, которые следует удалить вручную.retain,release,autoreleaseи другие вопросы

      Edit -> Convert -> To Objective-C ARC...

Новый Xcode с MRC

Если вы включите MRC, вы получите следующие ошибки (предупреждения)(но сборка будет успешной)

      //release/retain/autorelease/retainCount
'release' is unavailable: not available in automatic reference counting mode
ARC forbids explicit message send of 'release'
Другие вопросы по тегам