Сохранять циклы: Почему это так плохо?

Есть два объекта A и B. A создает B и сохраняет его. B имеет переменную экземпляра, которая указывает на A, сохраняя ее. Так что оба сохраняют друг друга. Некоторые люди говорят, что эта сильная связь больше не может быть нарушена.

Но действительно ли это так?

Если B освободит A, тогда A может легко освободить B, и поэтому B будет освобожден. A будет освобожден, как только он станет другим владельцем (я предполагаю, что кто-то) выпустит его.

Или эта проблема применяется только в случае, когда A не создает B, а просто содержит сильную ссылку на нее, сохраняя ее в переменной экземпляра? Я до сих пор не понимаю, почему эта связь не может быть снова разорвана.

Ответ 1

Циклы не плохие, но их часто избегают, потому что они могут сделать это сложным, чтобы убедиться, что у вас нет утечек памяти. Утечки возникают, особенно когда объекты "подсчет ссылок". На языке или системе, использующей подсчет ссылок, объект отслеживает количество ссылок, указывающих на него. Каждый раз, когда ссылка удаляется, счетчик уменьшается, когда счетчик становится равным нулю, ссылок нет, поэтому объект можно удалить.

Это обычно заботится о себе и работает нормально, без тщательного размышления. Если у вас есть группа объектов без циклов, и вы отбрасываете ссылку на корневой объект, тогда она будет удалена, это означает, что ссылки, которые она имеет на принадлежащие ей объекты, будут удалены, объекты, на которые делается ссылка, будут иметь свои подсчеты ссылок перейти к нулю. Они будут удалены, и каскад приведет к удалению всех объектов.

Но... если у вас есть цикл, этот каскад не работает. У вас может быть группа объектов, и вы не хотите их больше, поэтому вы бросаете единственную ссылку, которую вы имеете на эти объекты, но поскольку есть цикл, объекты ссылаются друг на друга. Это означает, что их счетчики ссылок никогда не обращаются к нулю, и они не удаляются. Это утечка памяти.

Ясно, что вы можете провести тщательное управление и разбить циклы, прежде чем отказаться от ссылки на группу объектов, которые вам больше не нужны. Но... как я только что сказал, это требует тщательного управления. Очень легко ошибиться. Это одна из основных причин утечки памяти.

Чтобы избежать риска утечек и сложной работы по правильному разбиению циклов, когда вам больше не нужна группа объектов, программисты обычно стараются избегать циклов. Это становится более важным в крупных проектах со многими программистами, где никто не понимает всю систему. Если бы были циклы, программистам приходилось следить и долго проводить изучение кода друг друга, чтобы избежать циклов.

Некоторые языки с сборщиками мусора (например, С#) могут удалить группу объектов, которые больше не нужны, даже если группа содержит циклы.

Ответ 2

Сохранение цикла может быть нарушено, если вы знаете об этом. Обычно это приводит к неприятным ошибкам (утечка памяти). В вашем примере:

A* a = [[A alloc] initAndCreateB];

Теперь экземпляр без имени B (созданный A) имеет счетчик сохранения 1. Поскольку мы удерживаем ссылку на A, а анонимный экземпляр B содержит сильную ссылку на A, счетчик сохранения равен 2.

Скажем, мы закончили с использованием A:

[a release];
return 12;

Теперь значение A останется 1. Это не будет выпущено, память потеряна. Вот почему удержание циклов плохое.

Ответ 3

Способ разрыва цикла удержания состоит в том, чтобы иметь отдельный метод "закрыть".

то есть.

A retains B
B retains A

когда вы закончите, вызовите метод (я назову его "close" ) на A, где выпуски A. Затем вы можете освободить A, и весь цикл будет выпущен (при условии, что в другом месте нет сохранений).

Ответ 4

Проблема заключается в следующем: A указывает на B и сохраняет B, а B указывает и сохраняет A. Когда других ссылок на A или B нет, не будет возможности их освободить, поскольку у приложения нет любые ссылки на них в этот момент. Это называется эталонным циклом, и он представляет собой тип утечки памяти, общий в любой системе подсчета ссылок. То, как это решается на большинстве языков высокого уровня, - это использование сборки мусора, а не подсчета ссылок.