Как управлять unsafe_unretained ivars/properties?

Я запустил target-c и iOS пару недель назад (стоит иметь в виду), и я заранее прошу прощения за ужасную диаграмму!!

введите описание изображения здесь

Диаграмма выше показывает структуру моих звонков в веб-сервис. Тонкие стрелки обозначают объект, создающий другой объект, в то время как толстые стрелки обозначают объект, содержащий сильную (сохраненную) ссылку на указанный объект.

Я считаю, что это содержит так называемую "круговую ссылку" и создаст проблемы, когда дело доходит до освобождения объектов.

Я понимаю, что простым ответом было бы заменить некоторые сильные ссылки на слабые, что я хотел бы сделать, за исключением того, что мой проект также ориентирован на iOS 3.2 (не мое решение - я не могу изменить этот факт!), Итак, я считаю правильным сказать, что вместо этого я должен использовать __unsafe_unretained, но я весьма обеспокоен тем фактом, что они не будут автоматически обнуляться, так как у меня возникнут проблемы EXC_BAD_ACCESS, когда объекты будут освобождены...

Поэтому моя проблема, во-первых, в том, что у меня есть круговые ссылки Чтобы решить, я должен был бы использовать __unsafe_unretained, что приводит к моей второй проблеме: как правильно управлять этим?

Вопрос, который может быть связан с этим: как NSURLConnection управляет своими сильными ссылками? Я слышал из разных источников, что он сохраняет свой делегат? Итак... если я сохраню NSURLConnection (и я также являюсь его делегатом), и он сохранит меня, это также будет круговая ссылка, нет? Как это обойти мою проблему?

Любой совет очень приветствуется!

С уважением, Ник

2 ответа

Решение

Когда родитель имеет ссылку на дочерний объект, он должен использовать строгую ссылку. Когда у дочернего элемента есть ссылка на его родительский объект, он должен использовать слабую ссылку, называемую unsafe_unretained.

По соглашению, отношения делегатов в iOS обычно являются слабыми ссылками, поэтому вы обнаружите, что большинство свойств делегатов в собственных классах Apple объявлены как unsafe_unretained.

Таким образом, ваш контроллер сохраняет сервисы, которые он использует, но сервисы слабо связаны с контроллером. Таким образом, если контроллер освобожден, вся партия может быть безопасно удалена без каких-либо циклических ссылок.

Опасность заключается в том, что если веб-служба выполняет какую-то долгосрочную задачу, и контроллер освобождается до его завершения, служба остается с висящим указателем на его теперь освобожденный делегат. Если он попытается отправить сообщение делегату, например "Я закончил", произойдет сбой.

Есть несколько подходов, которые помогут решить эту проблему (они не являются взаимоисключающими - вы должны попытаться сделать их все, когда это возможно):

1) Всегда устанавливайте свойства делегатов ваших служб равными nil в методе dealloc вашего контроллера. Это гарантирует, что когда контроллер освобождается, ссылки на делегаты на него устанавливаются равными nil (своего рода грубый, ручной эквивалент того, что слабые ссылки ARC делают автоматически).

2) При создании ваших собственных классов обслуживания, имеющих делегаты, сделайте так, чтобы они сохранили свой делегат во время работы, а затем освободите делегат, когда они будут сделаны. Таким образом, объект делегата не может быть освобожден, пока служба все еще отправляет ему сообщения, но он все равно будет освобожден, как только служба будет завершена (NSTimer и NSURLConnections оба работают таким образом - они сохраняют свой делегат во время работы и освобождают его когда они будут сделаны).

3) Старайтесь не иметь долгосрочные службы, принадлежащие чему-то временному, например контроллеру представления. Рассмотрите возможность создания одноэлементных объектов (экземпляров общих статических объектов), которые владеют вашими службами, чтобы служба могла выполнять свою работу в фоновом режиме независимо от того, что происходит в слое представления. Контроллер все еще может вызывать службу, но не владеет ею - служба принадлежит статическому объекту, который будет существовать в течение всего времени работы приложения, поэтому риск утечек или преждевременных выпусков отсутствует. Служба может связываться с контроллером через NSNotifications вместо вызовов делегатов, поэтому нет необходимости иметь ссылку на объект, который может исчезнуть. NSNotifications - отличный способ общаться между несколькими классами без создания циклических ссылок.

Все ваши вопросы и проблемы верны, и эта проблема с предыдущим использованием assign (теперь лучше назван __unsafe_unretained) именно поэтому Apple разработала автоматическое обнуление для weak, Но мы справились достаточно безопасно assign Делегаты на протяжении многих лет, так что, как вы подозреваете, есть способы сделать это.

Во-первых, на практике вы всегда должны очистить себя как делегата, когда вы выпускаете объект, для которого вы были делегированы. Pre-ARC, это было традиционно сделано в dealloc:

- (void)dealloc {
  [tableView_ setDelegate:nil];
  [tableView_ release];
  tableView_ = nil;
}

Вы все еще должны включить это setDelegate:nil в вашем dealloc если delegate является __unsafe_unretained, Это решит наиболее распространенную форму проблемы (когда делегат освобождается перед делегирующим объектом).

относительно NSURLConnectionВы также правы, что он сохраняет свой делегат. Это нормально, потому что срок его службы обычно намного меньше, чем у его делегата (по сравнению с делегатом табличного представления, который почти всегда имеет тот же срок жизни, что и табличное представление). См. " Как обойти / обработать ошибки делегирования EXC_BAD_ACCESS? Obj C " для более подробного обсуждения этого в контексте до ARC (те же самые концепции применимы в новом мире сильных / слабых).

Другие вопросы по тегам