iPhone: имеет ли смысл когда-либо сохранять объект своим делегатом? - PullRequest
2 голосов
/ 06 мая 2010

В соответствии с правилами управления памятью в мире, не собираемом мусора, нельзя оставлять вызывающий объект в делегате. Сценарий выглядит так:

У меня есть класс, который наследуется от UITableViewController и содержит панель поиска. Я запускаю дорогие поисковые операции во вторичном потоке. Все это делается с экземплярами NSOperationQueue и подклассами NSOperation. Я передаю контроллер как делегат, который придерживается протокола обратного вызова в NSOperation.

В некоторых случаях происходит сбой приложения, потому что, как только элемент выбран из UITableViewController, я отклоняю его, и, таким образом, его счетчик удерживания становится равным 0, и для него вызывается dealloc. Делегат не смог отправить свое сообщение вовремя, поскольку результаты передаются примерно в то же время, когда происходит освобождение.

Должен ли я оформить это по-другому? Должен ли я вызвать retain на моем контроллере от делегата, чтобы убедиться, что он существует, пока сама операция NSO не будет освобождена? Это приведет к утечке памяти? Прямо сейчас, если я установлю удержание на контроллере, сбои исчезнут. Однако я не хочу терять память и должен понимать, есть ли случаи, когда сохранение делегата имеет смысл.

Просто подведу итог.

UITableViewController создает NSOperationQueue и NSOperation, которые встраиваются в очередь. UITableViewController передает себя в качестве делегата NSOperation. NSOperation вызывает метод на UITableViewController, когда он готов. Если я сохраню UITableViewController, я гарантирую, что он есть, но я не уверен, что у меня утечка памяти. Если я использую только свойство assign, то возникают крайние случаи, когда UITableViewController получает dealloc'd, и objc_msgSend () вызывается для объекта, который не существует в памяти, и неизбежен сбой.

Ответы [ 4 ]

3 голосов
/ 06 мая 2010

В общем случае делегат владеет объектами, которые он установил в качестве делегата, или, по крайней мере, сохраняет ссылки на них прямо или косвенно. В методе dealloc делегат должен либо освободить все объекты, делегатом которых он является, таким образом, чтобы предотвратить будущие обратные вызовы, такие как недействительный NSTimer, либо очистить член делегата от тех объектов, которые могут сохраняться.

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

Вы можете сделать свойство делегата вашего NSOperation атомарным, не устанавливая неатомный флаг и синтезируя геттер и сеттер. Или вы можете использовать executeSelectorOnMainThread перед использованием члена делегата.

Напомним, что обычно существует лучшее решение, чем сохранение делегата.

2 голосов
/ 09 февраля 2011

Мне действительно интересно, хотя правила «не сохранять ваш делегат» по-прежнему применяются конкретно к многопоточным отношениям объект / делегат, особенно к тому, о котором вы пишете.Я нашел свой путь к этому вопросу, потому что я нахожусь в точно такой же ситуации: я выполняю асинхронную сетевую операцию конечной, но непредсказуемой длины (она завершится и завершится самостоятельно "в ближайшее время") в NSOperation и использует делегатхранится в NSOperation для уведомления о завершении операции обратно к исходному объекту-запросчику.

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

Обратите внимание, что пометка свойства делегата атомарным, как предполагает drawonward, сама по себе недостаточна для защиты от условий гонки!Установка свойства как атомарного означает только то, что читатель или создатель свойства будет записывать или читать все целое значение за раз, ничего больше .Следующая последовательность может привести к сбою:

(поток NSOperation): NSOperation завершен и готовится уведомить объект делегата.Код атомарно считывает значение свойства делегата, готовящегося к вызову его метода done.

----> Поток переключается на основной

(основной поток), запрашивающий объект - dealloc 'd, а в -(void)dealloc (атомарно!) устанавливается свойство делегата NSOperation равным nil, dealloc завершается, и объект теперь исчезает

-----> Переключение потока на поток NSOperation

(Поток NSOperation) вызывает [делегат allDone], и ​​программа падает (если вам повезет), потому что объект делегата пропал.Если повезет.Возможно, что-то еще было выделено в этом пространстве тем временем, и теперь у вас непредсказуемая коррупция, что забавно!

Ключевым моментом здесь является то, что цикл сохранения является временным по самой своей природе -NSOperation завершит и очистит все самостоятельно.Это не то же самое, что UITextField и UIViewController, в которых хранятся сохраненные ссылки друг на друга, которые никогда не исчезнут и, таким образом, утечка памяти.

Мне кажется, что сохранение делегата в случае асинхронного, ограниченного, самостоятельногоЗавершение операции является самой чистой и надежной реализацией.

РЕДАКТИРОВАТЬ:

, если вам абсолютно не нужно сохранять делегат, потому что это вызывает проблемы с памятью, тогда вместо этого объект делегата и объект операции должны использовать обаявный механизм блокировки для синхронизации доступа к указателю делегата, сохраненному в операции.«атомарные» свойства действительно не обеспечивают потокобезопасный протокол для маркировки указателя делегата nil.

Но я думаю, что действительно усложняется и полон условий гонки.Я думаю, по крайней мере, что вы должны в dealloc объекта делегата запустить протокол блокировки, чтобы убедиться, что указатель делегата операции безопасно установлен равным nil, так что произвольное чередование потоков не может ни при каких обстоятельствах вызывать объект делегата dealloc'd.

Рабское следование правилам иногда делает вещи намного сложнее, чем они должны быть.Лучше понять, что такое правила, почему они существуют, и поэтому вы знаете, когда имеет смысл (как я полагаю, это имеет место в этом конкретном сценарии) не следовать им, и каковы преимущества / недостатки в этом.

1 голос
/ 09 февраля 2011

Я подхожу к этому немного по-другому, и я не сохраняю делегатов.

  • Если мне нужно потерять представление, я отменяю операцию NSO.Я считаю, что это хороший дизайн: если возвращаемому значению потока некуда деваться, поток должен остановиться.

  • Я также видел крайний случай, когда поток не может быть остановлен.В этом случае, я просто обнуляю оставшегося делегата, чтобы быть уверенным, и проверяю, что делегат не равен нулю, прежде чем выполнять обратный вызов.он больше не нужен, особенно на iPhone, жует память и, на мой взгляд, плохой дизайн.

    Согласно ответу @ Bogatyr, простая проверка на nil перед вызовом [делегировать allDone] намного чище.1015 *

0 голосов
/ 06 мая 2010

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

ОднакоВсе эти вещи действительно только излечивают симптомы, а не болезнь. правильный способ сделать это обозначен drawonward.

...