В Objective-C Cocoa мы работаем с полуавтоматическим управлением памятью с подсчетом ссылок. При выделении памяти для объекта, сохранении объекта или вызове метода copy
на объекте счетчик сохранения (счетчик ссылок) увеличивается на 1. При вызове release
для объекта он уменьшает счетчик хранения на единицу. При вызове autorelease
для объекта, release
будет вызываться для объекта в некоторый момент в будущем (во время основного цикла выполнения, когда не выполняется ни один из вашего собственного кода, поэтому он не будет извлекать ссылку из под тобой как ты пытаешься его использовать). Когда счет сохранения достигает 0, объект может быть освобожден.
В общем, если вы звоните retain
на объект, вы сигнализируете о своем интересе к нему, и вы несете ответственность за совершение вызова release
или autorelease
в какой-то момент, когда вы не больше не интересует объект. Аналогично, если вы вызываете alloc
или copy
метод для объекта, вы сигнализируете о своей заинтересованности в объекте и должны сопоставить его с release
или autorelease
где-то вниз по линии.
Эта ссылка в значительной степени охватывает рекомендации, которые Apple использует (и вы должны использовать) для управления памятью: Простые правила для управления памятью в Какао
Давайте пройдемся по коду построчно:
ClassOne *pointer = [[ClassOne alloc]init];
pointer
указывает на вновь выделенный объект ClassOne с счетом сохранения 1, так как мы вызвали на нем alloc. Мы обязаны позвонить release
или autorelease
на pointer
в какой-то момент в будущем.
ClassTwo *foo = [[ClassTwo alloc]init], *foo2;
foo
указывает на вновь выделенный объект ClassTwo с счетом сохранения 1, так как мы вызвали на нем alloc. Мы обязаны позвонить release
или autorelease
на foo
в какой-то момент в будущем.
foo2
сейчас не указывает ни на что конкретное. Это не безопасно использовать.
foo2 = [foo add: pointer];
pointer
был добавлен к foo
(что бы это ни значило; мы не знаем реализацию). foo
мог бы вызвать retain
на pointer
, чтобы сообщить о своем интересе к нему, и добавить его в качестве поля, или он мог бы добавить pointer
к коллекции (в этом случае ответственность за вызов * 1052 лежит на коллекции). * на него при добавлении объекта и release
при удалении объекта). В любом случае, это не влияет на наш блок кода, поэтому нам все равно, что происходит под капотом
Ссылка, возвращаемая этим методом, может быть самой pointer
, или это может быть автоматически выпущенная копия pointer
; у нас нет доступа к API или реализации, чтобы сказать нам, какой.
В любом случае мы не обязаны звонить release
по этому объекту. Если бы у метода было copy
в имени или если бы мы вызвали retain
в возвращенной ссылке (например, foo2 = [[foo add:pointer] retain];
), то оставшееся количество было бы увеличено на 1, и мы должны были бы вызвать release
или autorelease
на нем.
[foo release];
Объект, на который ссылается foo
, был освобожден, что означает, что его счетчик хранения был уменьшен на 1. Для этого примера, это соединение с вызовом alloc
, которое мы сделали в строке 2, поэтому счетчик хранения уменьшится до 0 , что делает foo
право на освобождение.
В целом, однако, нам все равно, был ли объект освобожден или нет; нам просто нужно убедиться, что мы объединяем любые вызовы alloc
, copy
или retain
с одним и тем же числом вызовов release
или autorelease
. Если мы зарегистрируем интерес к объекту в любое время, мы обязаны освободить его, иначе у нас будут утечки памяти.
foo = foo2;
foo
теперь указывает на тот же объект, на который ссылается foo2
. Помните, что мы не вызвали метод alloc
или copy
, когда получили foo2
, и мы не зарегистрировали интерес к нему, вызвав retain
. Поскольку мы не обязаны звонить release
на foo2
, мы не обязаны звонить release
на foo
.
[pointer release];
Количество сохраненных элементов
pointer
было уменьшено на 1. Это могло привести к тому, что его количество сохраненных записей равно 0 или нет, это зависит от того, что foo
сделал с ним, когда мы добавили его. Тем не менее, нам все равно; мы завершили нашу ответственность до pointer
, позвонив по ней release
, чтобы соответствовать вызову alloc
, который мы сделали в начале. Хотя pointer
может все еще быть после этого вызова, мы не можем сделать такое предположение, и попытка что-либо сделать с объектом, на который ранее ссылался указатель, была бы ошибкой (хотя мы могли бы изменить pointer
, чтобы указывать на что-то другое свободно ).
[foo release];
Если автор этого кода следовал соглашениям Apple об управлении памятью, то в этом нет необходимости. Мы не обязаны звонить release
на foo
или foo2
(они указывают на один и тот же объект, помните). Это не приведет к поломке кода; вызывать что-либо по ссылке nil
по сути дела нельзя. Однако это может привести к путанице для любого, кто просматривает код.
Теперь автор этого кода мог нарушить соглашения об управлении памятью. Он мог бы сделать так, чтобы add
call возвратил копию pointer
без вызова autorelease
, и в этом случае вызывающий абонент несет ответственность за вызов release
для него. Это очень плохая форма, и если вам нужно столкнуться с кодом, который нарушает соглашение об управлении памятью, укажите, где вы его используете, и как он нарушает соглашение, чтобы избежать путаницы в будущем.