Еще один вопрос «Оставь, а потом отпусти» - PullRequest
1 голос
/ 02 июня 2011

Будучи новичком в Cocoa / Obj-C, я изучаю книгу Аарона Хиллегаса «Программирование какао для Mac OS X» и, не говоря уже о том, что теперь у нас есть возможность использовать GC, чтобы избежать всех этих рассуждений, - Я не уверен, что понял причину некоторых из этих задержек.

В частности, в одном из примеров Аарон приводит в качестве хорошей практики программирования:

- (void) setFoo:(NSCalendarDate *)x
{
    [x retain];
    [foo release];
    foo = x;
}

У меня нет причины сохранять экземпляр x в первой строке метода:

[x retain];

Область действия этого экземпляра - просто метод set, верно? При выходе из области действия метода экземпляр x должен быть освобожден в любом случае нет? Кроме того, при назначении x для foo:

foo = x;

foo в любом случае будет указывать на x ячеек памяти и, следовательно, будет увеличивать счетчик остаточного объекта, не так ли? Это должно гарантировать, что память не будет освобождена.

Так в чем смысл? Конечно, я уверен, что что-то упустил, но не знаю, что именно.

* +1015 * Спасибо, Фабрицио

1 Ответ

11 голосов
/ 02 июня 2011

Сохранять означает: Мне понадобится этот объект, чтобы остаться, его нельзя освобождать. Если x не будет сохранен, вероятно, произойдет следующее:

Вы назначаете x на foo, поэтому foo теперь указывает на адрес, где находится ваша NSCalendarDate. Кто-то освобождает или автоматически выпускает этот объект, его счетчик в конечном итоге падает до 0, и объект освобождается. Теперь ваш foo по-прежнему указывает на этот адрес, но больше нет действительного объекта. Некоторое время спустя создается новый объект, и случайно он находится по тому же адресу, что и ваш старый объект NSCalendarDate. Теперь ваш foo указывает на совершенно другой объект!

Чтобы предотвратить это, вам нужно retain. Вы должны сказать: пожалуйста, пока не освобождайте объект, он мне нужен. Когда вы закончите с ним, вы release это означает, что Мне больше не нужен объект, вы очистите его сейчас, если это больше никому не нужно.

Теперь о классическом задании из трех частей. Считайте, что ваш setFoo: будет выглядеть так:

- (void) setFoo:(NSCalendarDate *)x
{
    [foo release];
    [x retain];
    foo = x;
}

Это очень плохая идея. Предположим, что ваш объект является единственным, кто сохранил объект NSCalendarDate, и подумайте, что вы тогда сделаете: [self setFoo:foo];. Звучит глупо, но может случиться что-то подобное. Теперь поток будет таким:

  1. foo будет выпущен. Его счетчик сохранности теперь может упасть до 0, и объект будет освобожден.
  2. Ой, мы пытаемся сохранить и получить доступ к освобожденному объекту.

Вот почему вы всегда сначала retain новый объект, а затем release старый объект.

Если вы работаете с фоном Java или .NET, очень важно понимать, что переменная типа Foo * содержит только адрес вашего объекта, и ничего более. В Java или .NET переменная, которая указывает на объект, автоматически «сохраняет» его, если хотите. Не так в Objective-C (в средах без GC). Вы можете рассматривать переменную типа Foo * как слабую ссылку, и вам явно нужно сообщить Objective-C, нужен ли вам этот объект по этому адресу или нет.

...