Понимание подсчета ссылок с Какао и 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-е издание) Аарона Хиллегаса - очень хорошо написанная книга с множеством замечательных примеров. Это читается как учебник.
- Если вы действительно погружаетесь, вы можете отправиться на ранчо Большого ботаника. Это учебный центр, которым руководит Аарон Хиллегас - автор упомянутой выше книги. Я посещал курс "Введение в какао" там несколько лет назад, и это был отличный способ учиться.
Если вы понимаете процесс сохранения / освобождения, то есть два золотых правила, которые очевидны для опытных программистов Какао, но, к сожалению, они редко излагаются ясно для новичков.
Если функция, которая возвращает объект, имеет
alloc
,create
или жеcopy
в его названии объект ваш. Вы должны позвонить[object release]
когда вы закончите с этим. Или жеCFRelease(object)
, если это объект Core-Foundation.Если в его имени нет одного из этих слов, то объект принадлежит кому-то другому. Вы должны позвонить
[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 есть бесплатный скринкаст
Ответы выше дают четкие подтверждения того, что говорится в документации; проблема, с которой сталкиваются большинство новых людей, - это незарегистрированные случаи. Например:
Авто-релиз: в документах говорится, что он запустит релиз "в какой-то момент в будущем". КОГДА?! По сути, вы можете рассчитывать на то, что объект находится рядом, пока не вернете свой код обратно в цикл системных событий. Система МОЖЕТ освободить объект в любое время после текущего цикла события. (Я думаю, Мэтт сказал это раньше.)
Статические строки:
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
Правильный способ освобождения объекта:
- . Если использовать только этот прием - висящий указатель. Потому что он по-прежнему может указывать на объект в куче и отправлять сообщение. Например, A делает +1, B просто делает ссылку (без +1), A делает выпуск -1, счетчик == 0, объект освобождается, но B имеет ссылку на кучу
-
= 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)
![](/images/44ddb3409f43dfe86f0abd5b6f7d761c854a4d35.png)
![](/images/fc9b637379960c4be426a91b0bd5c3e930b90982.png)
Проверьте, включен/отключен ли 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'