Чому делегатам Objective-C зазвичай присвоюється властивість замість збереження?


176

Я переглядаю чудовий блог, який підтримує Скотт Стівенсон, і я намагаюся зрозуміти основоположну концепцію Objective-C щодо присвоєння делегатам властивості "призначити" проти "зберегти". Зауважте, обидва однакові у сміттєвому середовищі. Мене найбільше хвилює середовище, що не базується на GC (наприклад, iPhone).

Безпосередньо з блогу Скотта:

"Ключове слово присвоїти буде генерувати сеттер, який присвоює значення змінній екземпляру безпосередньо, а не копіює або зберігає його. Це найкраще для примітивних типів, таких як NSInteger і CGFloat, або об'єктів, якими ви безпосередньо не володієте, наприклад, делегатів."

Що означає, що ви безпосередньо не володієте об'єктом делегата? Я, як правило, затримую своїх делегатів, тому що якщо я не хочу, щоб вони відходили в безодню, утримуватимуться, щоб подбати про це за мене. Я зазвичай абстрагую UITableViewController подалі від відповідного джерела даних і також делегую. Я також зберігаю саме цей предмет. Я хочу переконатися, що він ніколи не зникає, тому мій UITableView завжди має свого делегата.

Чи може хтось далі пояснити, де / чому я помиляюся, тож я можу зрозуміти цю загальну парадигму в програмуванні Objective-C 2.0 щодо використання властивості присвоєння делегатам замість збереження?

Дякую!


Позначається з "делегатами" і без "iphone".
Квінн Тейлор

чому присвоєння делегата замість копії (як NSString?)
OMGPOP

Відповіді:


175

Причина того, що ви уникаєте утримувати делегатів, полягає в тому, що вам потрібно уникати циклу збереження:

A створює B A встановлює себе делегатом B ... A випускається його власником

Якби B утримував A, A не було б звільнено, оскільки B є власником A, таким чином, угода A ніколи не зателефонується, що призведе до витоку і A, і B.

Ви не повинні турбуватися про те, що йде геть, тому що він є власником B і, таким чином, позбавляється від нього у взаємодії.


Я не згоден, Майк. Я щойно знайшов проблему, коли модал має делегата, який відхиляє модаль. Але коли я роблю попередження про пам'ять у модальному режимі, воно звільняє делегата. Тоді, коли я йду відхиляти модаль, делегат - це нуль. Збій.
Пол Шапіро

Гаразд, я не погоджуюся, але ви праві, що це вада дизайну. Я дізнався, що я передаю свій дзвінок про звільнення через дитячий клас реального звільнення. Цей дочірній клас був звільнений і не міг передати делегату контейнера модалу для відхилення. Я змінив його, щоб передати вказівник на остаточного делегата, і він не був звільнений при попередженні пам'яті, і все добре.
Пол Шапіро

2
Ваш код ніколи не повинен бути написаний таким чином, що нульовий делегат викликає його збій. Тільки об'єкт, що володіє, повинен мати посилання на володіння. Коли dealloc'd, він повинен встановити делегати об'єктів, що належать до нуля, перш ніж випускати їх. Тоді будь-які повідомлення, надіслані нульовому делегату, просто ігноруються. Однак передача об’єкта нуля в повідомленні може вийти з ладу. Просто переконайтеся, що ви не поводитесь з делегатами таким чином.
Девід Гіш

Зачекайте - чи не так weakце? Питання в тому, чому використовувати assignзамість weak?
wcochran

3
@wcochran: Ні, це питання чому використовувати assignзамість retain. Питання старіше за ARC; weakі strong(останні були синонімом retain) не існували до введення ARC. Слід запитати про weakпорівняно з assignокремо.
Пітер Хосей

44

Оскільки об'єкт, що надсилає повідомлення про делегат, не є власником делегата.

Багато разів - це навпаки, як коли контролер встановлює себе як делегат подання чи вікна: контролеру належить перегляд / вікно, тому якби перегляд / вікно володів його делегатом, обидва об'єкти мали б один одного. Це, звичайно, цикл утримування, подібний до витоку з тим же наслідком (предмети, які повинні бути мертвими, залишаються живими).

В іншому випадку об'єкти є однолітками: жоден не володіє іншим, ймовірно, тому, що вони обидва є одним і тим же третім об'єктом.

У будь-якому випадку об'єкт з делегатом не повинен зберігати свого делегата.

(До речі, є принаймні один виняток. Я не пам’ятаю, що це було, і я не думаю, що це було вагомою причиною.)


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

Якщо ви чомусь тримаєтесь подалі від АРК, принаймні змініть assignвластивості, які вказують на об’єкти unsafe_unretained, які роблять очевидним, що це невідоме, але не нульове посилання на об'єкт.

assign залишається придатним для не об’єктних значень як для ARC, так і для MRC.


13
NSURLConnectionзберігає свого делегата.

Так, використовуйте weak, але це не відповідає початковому питанню: Чому Apple використовує assignзамість weak?
wcochran

@wcochran: Первісне питання було "чому властивості делегата надані, assignа не збережені "; weakне існувало, коли його запитали. Ваше запитання - це інше, яке вам слід задати окремо. Я був би радий відповісти на це.
Пітер Хосей

@wcochran та Peter, це питання було задано десь ще?
Містер Роджерс

17

Зауважте, що коли у вас є присвоєний делегат, дуже важливо завжди встановлювати це значення делегу на нуль, коли об'єкт буде розміщений - тому об'єкт завжди повинен бути обережним, щоб звести посилання делегата в dealloc, якщо він не має це робиться в інших місцях.


"Зауважте, що коли у вас є призначений делегат, дуже важливо завжди встановлювати значення делегата на нуль, коли об'єкт буде розміщений" Чому?
Пітер Хосей

2
Оскільки будь-який посилання зліва встановлений, буде недійсним після розміщення об'єкта (вказівка ​​на пам'ять більше не виділяється на тип очікуваного об'єкта) - і, таким чином, спричинить збій, якщо ви спробуєте використовувати його. Ознакою цього в налагоджувачі є те, коли налагоджувач стверджує, що деяка змінна має тип, який здається абсолютно неправильним, ніж те, що змінна насправді оголошена.
Кендалл Гельмстетер Гельнер

1
Це необхідно лише в тому випадку, якщо об'єкт, якому ви є делегатом, зберігається в іншому джерелі, наприклад, таймері або іншому асинхронному зворотному виклику. В іншому випадку він буде розблокований після його звільнення і не буде намагатися викликати делегатні методи.
Ендрю Пуліот

@Andrew: Це правда, але якщо ти завжди робиш практику знімати делегатів, ти не забудеш, коли це має значення, або якщо ти випадково затримаєш утримуваний об'єкт, і він все одно залишатиметься навколо. Якщо ви скасуєте делегата, то результат - це лише витік, замість витоку з подальшим збоєм.
Kendall Helmstetter Gelner

1

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

Акутально призначити найкраще для примітивних типів, таких як NSInteger та CGFloat, або об'єктів, якими ви безпосередньо не володієте, наприклад, делегатів.


Це скопійовано відповідно з цитати ОП та відповідно прийнятої відповіді, чи не так?
dakab
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.