Почему делегатам Objective-C обычно дают свойство назначать, а не сохранять?

176

Я пролистываю замечательный блог, который ведет Скотт Стивенсон, и пытаюсь понять фундаментальную концепцию Objective-C, заключающуюся в том, чтобы назначать делегатам свойство 'assign' против 'retain'. Обратите внимание, что в среде сборки мусора они одинаковы. В основном меня интересует не основанная на GC среда (например, iPhone).

Прямо из блога Скотта:

«Ключевое слово assign создаст установщик, который присваивает значение переменной экземпляра напрямую, а не копирует или сохраняет его. Это лучше всего подходит для примитивных типов, таких как NSInteger и CGFloat, или для объектов, которыми вы непосредственно не владеете, таких как делегаты».

Что это значит, что вы непосредственно не являетесь владельцем объекта делегата? Обычно я оставляю своих делегатов, потому что, если я не хочу, чтобы они уходили в пропасть, удержание позаботится об этом за меня. Я обычно абстрагирую UITableViewController от его соответствующего источника данных и делегирую также. Я также сохраняю этот конкретный объект. Я хочу убедиться, что он никогда не исчезнет, ​​поэтому у моего UITableView всегда есть свой делегат.

Может кто-нибудь еще объяснить, где / почему я не прав, чтобы я мог понять эту общую парадигму в программировании Objective-C 2.0 использования свойства assign для делегатов вместо сохранения?

Спасибо!

Plumenator
источник
Повторно помечены с «делегатами» и без «iphone».
Куинн Тейлор
почему делегат назначается вместо копии (например, NSString?)
OMGPOP

Ответы:

175

Причина, по которой вы избегаете сохранения делегатов, заключается в том, что вам нужно избегать цикла хранения:

A создает B A устанавливает себя в качестве делегата B ... A освобождается его владельцем

Если бы B сохранил A, A не был бы освобожден, так как B владеет A, таким образом, разлочка A никогда не была бы вызвана, вызывая утечку и A, и B.

Вам не стоит беспокоиться об уходе А, потому что он владеет Б и, таким образом, избавляется от него в dealloc.

Эндрю Пулио
источник
Я не согласен, Майк. Я только что нашел проблему, когда модал имеет делегата, который отклоняет модал. Но когда я делаю предупреждение памяти в модале, он освобождает делегата. Затем, когда я иду, чтобы отклонить мой модал, делегат равен нулю. Краш.
Пол Шапиро
Хорошо, я не согласен, но вы правы, что это недостаток дизайна. Я узнал, что передавал свой вызов по увольнению через детский класс настоящего увольняющего. Этот дочерний класс был освобожден и не мог перейти на контейнерный делегат модала для увольнения. Я изменил его, чтобы передать указатель на последнего делегата, который не был освобожден при предупреждении памяти, и все хорошо.
Пол Шапиро
2
Ваш код никогда не должен быть написан так, чтобы нулевой делегат вызывал его сбой. Только владелец объекта должен иметь ссылку на владельца. Когда dealloc'd, он должен установить делегатов принадлежащих объектов на ноль, прежде чем освобождать их. Тогда любые сообщения, отправленные ноль-делегату, просто игнорируются. Однако передача nil-объекта в сообщении может привести к сбою. Просто убедитесь, что вы не имеете дело с делегатами таким образом.
Дэвид Гиш
Подожди - разве weakне так? Вопрос в том, зачем использовать assignвместо weak?
wcochran
3
@wcochran: Нет, этот вопрос, почему используйте assignвместо retain. Вопрос старше, чем ARC; weakи strong(последний является синонимом retain) не существовало до появления ARC. Вы должны задать свой вопрос о weakпротив assignотдельно.
Питер Хоси
44

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

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

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

В любом случае, объект с делегатом не должен сохранять своего делегата.

(Между прочим, есть по крайней мере одно исключение. Я не помню, что это было, и я не думаю, что для этого были веские причины.)


Приложение (добавлено 2012-05-19): в ARC вы должны использовать weakвместо assign. Слабые ссылки устанавливаются nilавтоматически, когда объект умирает, исключая возможность того, что делегирующий объект в конечном итоге отправит сообщения мертвому делегату.

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

assign остается подходящим для необъектных значений как в ARC, так и в MRC.

Питер Хоси
источник
13
NSURLConnectionсохраняет свой делегат.
Да, используйте weak, но это не отвечает на первоначальный вопрос: почему Apple использует assignвместо weak?
wcochran
@wcochran: первоначальный вопрос был «почему свойства делегата передаются, assignа не сохраняются »; weakне существовало, когда его спросили. Ваш вопрос другой, который вы должны задать отдельно. Я был бы рад ответить на это.
Питер Хоси
@wcochran и Питер, этот вопрос был задан где-то еще?
мистер Роджерс
17

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

Кендалл Хельмштеттер Гелнер
источник
«Обратите внимание, что когда у вас есть назначенный делегат, очень важно всегда устанавливать значение этого делегата равным nil, когда объект будет освобожден». Почему?
Питер Хоси
2
Поскольку любая ссылка, оставленная установленной, будет недействительной после освобождения объекта (указывая на память, больше не выделенную для ожидаемого типа объекта) - и, таким образом, вызовет сбой, если вы попытаетесь использовать его. Признаком этого в отладчике является тот факт, что отладчик утверждает, что некоторая переменная имеет тип, который кажется совершенно неверным по сравнению с тем, как переменная фактически объявлена.
Кендалл Хельмштеттер Гелнер
1
Это необходимо только в том случае, если объект, делегатом которого вы являетесь, сохраняется в другом источнике, таком как таймер или другой асинхронный обратный вызов. В противном случае он будет освобожден после его освобождения и не будет пытаться вызывать методы делегата.
Эндрю Пулио
@Andrew: Это правда, но если вы всегда практикуете исключение делегатов, то вы не забудете, когда это имеет значение, или если вы случайно перенесете удерживаемый объект, и он все равно останется. Если вы удалите делегат, то результатом будет просто утечка, а не утечка, за которой следует сбой.
Кендалл Хельмштеттер Гелнер
1

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

Оперативное назначение лучше всего подходит для примитивных типов, таких как NSInteger и CGFloat, или для объектов, которыми вы не владеете напрямую, таких как делегаты.

Пунит Шарма
источник
Это скорее скопировано из цитаты ОП и принятого ответа соответственно, не так ли?
Дакаб