Еще один вопрос о сохранении, а затем о выпуске

Будучи новичком в 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 ячеек памяти и, следовательно, будет увеличивать счетчик сохранения указанного объекта, нет? Это должно гарантировать, что память не будет освобождена.

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

Спасибо, Фабрицио.


person Fabrizio Prosperi    schedule 02.06.2011    source источник
comment
С другой стороны, приведенный здесь код на самом деле подчеркивает хорошую практику. В некоторых сценариях возможно, что «x» и «foo» уже могут указывать на один и тот же объект. В этом случае, если вы следуете типичному потоку установки '[foo release], then foo = [x keep]', то вы сначала освобождаете 'x'/'foo' (), а затем снова сохраняете его. Если счетчик сохранения объекта равен «1» во время вызова «setFoo:», то это означает, что «x»/«foo» сначала освобождается, а ТОГДА освобожденному объекту передается сообщение «retain». Вместо этого, следуя схеме Аарона, сохраняя сначала «x», а затем отпуская «foo», вы избегаете этой проблемы.   -  person Dev Kanchen    schedule 22.06.2011


Ответы (1)


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

person DarkDust    schedule 02.06.2011
comment
Спасибо за отличное объяснение DarkDust, оно развеивает сомнения человека, но единственный момент, который я, вероятно, не прояснил в своем вопросе, это: разве строка foo = x; не увеличит количество сохраненных объектов, на которые x указывает в любом случае...? Вот почему я считаю принудительное удержание бесполезным... - person Fabrizio Prosperi; 02.06.2011
comment
О, я только что перечитал последнюю часть про слабую ссылку! Это мой ответ! Интересно, почему Аарон не выделил это жирным шрифтом... это огромное отличие от других языков, к которым я привык. Спасибо :) - person Fabrizio Prosperi; 02.06.2011
comment
@Fabrizio Prosperi: Возможно, Аарон не выделил это из-за своего прошлого. Когда он написал первое издание, .NET еще не существовало, а Java была еще молода. У большинства программистов тогда был опыт работы с С, или, может быть, с Паскалем или другими процедурными языками. Единственным заслуживающим внимания языком GC был LISP. Для этих людей такое назначение переменной только копирует адрес и больше ничего не делает абсолютно естественно и не нуждается в объяснении. Только с появлением в последние годы языков/сред, управляемых сборщиком мусора, это изменилось. - person DarkDust; 02.06.2011
comment
Кто-то сообщил мне об этом сегодня, и я написал тестовую функцию только для этого [foo setFoo:foo], и в setFoo я сначала не сохраняю переданное значение, я просто отпускаю foo и назначаю его foo = [passedValue keep], и он работает нормально, проблем с памятью нет... - person Shizam; 28.07.2011
comment
@Shizam: Я готов поспорить, что в вашем случае объект также был сохранен в другом месте. Другими словами, счетчик сохранения вашего аргумента foo не был равен 1, когда вы запускали свой метод setFoo:. - person DarkDust; 28.07.2011