Ответ 1
С Objective-C Cocoa мы работаем с полуавтоматическим управлением памятью подсчета ссылок. При распределении памяти для объекта, сохранении объекта или вызове метода copy
для объекта, счетчик удержания (счетчик ссылок) увеличивается на 1. При вызове release
объекта, он уменьшает количество задержек на единицу. При вызове autorelease
на объекте в какой-то момент в будущем будет вызываться release
в объекте (во время цикла основного запуска, когда ни один из ваших собственных кодов не выполняется, поэтому он не вытащит ссылку из под вами, когда вы пытаетесь его использовать). Когда счетчик сохранения достигает 0, объект может быть освобожден.
В общем, если вы вызываете retain
на объект, вы сигнализируете о своем интересе к нему, и вы несете ответственность за вызов release
или autorelease
в какой-то момент, когда вы не являетесь больше интересуется объектом. Аналогично, если вы вызываете метод alloc
или copy
для объекта, вы сигнализировали о своем интересе к объекту и должны соответствовать ему с release
или autorelease
где-то по строке.
Эта ссылка в значительной степени охватывает руководящие принципы, которые Apple использует (и вы должны использовать) для управления памятью: Простые правила управления памятью в Cocoa
Пропустите код по строке:
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
в коллекцию (в этом случае ответственность за сбор для вызова retain
на нем, когда объект добавлен, и 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
(они указывают на один и тот же объект, помните). Это не приведет к тому, что код break; вызывает что-либо на ссылке nil
, по существу, не работает. Однако это может вызвать путаницу для тех, кто просматривает код.
Теперь автор этого кода, возможно, нарушил соглашения управления памятью. Возможно, он сделал, что вызов add
возвращает копию pointer
без вызова autorelease
на нем, и в этом случае вызывающий отвечает за вызов release
на нем. Это очень плохая форма, и если вам нужно запустить код, который нарушает соглашение об управлении памятью, документ, в котором вы его используете, и как он нарушает соглашение, чтобы избежать путаницы в будущем.