Тема «слабых» ссылок при установлении функций-слушателей (англ. listener) для событий в Action Script уже подымалась не раз несколько лет подряд. Я, например, стараюсь следить за всеми новостями от Adobe, давно знавший о их существовании, тем не менее, понял их суть совсем недавно, только после использования профайлера в Flex Builder.
Известный товарищ gskinner ещё в далёком 2006 году удивлялся почему все слушатели по умолчанию не подключаются в режиме weak reference, а некоторые ребята на Хабре, в комментах к статье про контексты функций, вообще считают, что «слабые» ссылки от нечистого и все операции по удалению онных нужно производить исключительно вручную!
Вместо пересказывания справочной информации я приведу пример, в котором я вижу исключительную полезность использования «слабых» ссылок.
public class GuestbookEntry
{
public function get comments() : ArrayCollection
{
return _comments;
}
public function set comments(value : ArrayCollection) : void
{
if (_comments != null)
{
_comments.removeEventListener(CollectionEvent.COLLECTION_CHANGE,
onCommentsChange);
}
_comments = value;
if (value != null)
{
value.addEventListener(CollectionEvent.COLLECTION_CHANGE,
onCommentsChange, false, 0, true);
}
}
protected function onCommentsChange(event : CollectionEvent) : void
{
switch (event.kind)
{
case CollectionEventKind.ADD :
CommentEntry(event.items[0]).guestbookEntry = this;
break;
}
}
private var _comments : ArrayCollection;
}
Представим, у нас есть гостевая книга, и тип её записи GuestbookEntry представленый выше. У этого типа есть коллекция комментариев. Пусть комментарий описывается типом CommentEntry. Где-то есть форма для добавления новых комментов, у нас есть требование архитектуры проекта, что-бы каждый коммент имел ссылку на запись книги посредством свойства guestbookEntry.
Экземпляры GuestbookEntry скорее всего будут получены от сервера, неважно. В сетере свойства comments я подписываюсь на событие изменения коллекции, что-бы контроллировать момент добавления нового коммента. Как видим, ссылка на функцию onCommentsChange передаётся в коллекцию как «слабая» (последний аргумент метода addEventListener).
Во время профайлинга приложения с подобным кодом, до использования в нём «слабой» ссылки, в памяти оставались все когда-либо созданые экземпляры класса GuestbookEntry. Инспектор «висячих» (англ. loitering) объектов показывал две связи между экземплярами GuestbookEntry и CommentEntry: одна посредством свойства guestbookEntry, другая, косвенная, посредством функции onCommentsChange (её экземпляр хранился в коллекции комментов, а savedThis (по-сути, контекст функции) у функции ссылался на запись гостевой).
На сколько я понял, сборщик мусора в Flash Player автоматически очищает память от связаных между собой объектов только через прямые ссылки (циклические вчастности), если же есть косвенные (все подписки на события) связи, он — бессилен. Сдесь нам и помагают «слабые» ссылки. Никакого риска использования их нет, так как это, зачастую, последние оставшиеся ссылки между объектами, готовыми к удалению.
Внимательный читатель, который любит Фаулера, заметит и спросит о том, что нам мешает удалять в ручную такие ссылки? А я отвечу — ничего не мешает, но подобные решения будут громоздкими и некрасивыми. В данном случае, мне нужно было бы реализовать в типе GuestbookEntry, что-то вроде метода dispose():
public function dispose() : void
{
comments = null;
}
И вызывать этот метод во всех местах потенциального «прощания» с экземплярами записей гостевой книги. Скучно, старомодно и не практично.
UPD: Поразмыслив с коллегами по работе, выяснилось пару аспектов когда «слабые» ссылки могут подложить вам свинью. Например, если у вас остался «неприкаянный» объект, на который ссылаются «слабой» ссылкой, до момента уничтожения его сборщиком мусора, он будет получать события (ведь ссылка есть) и возможно, обработка этих событий будет вами не запланирована, так как вы, уже, мысленно «распрощались» с объектом. Что-бы избежать подобных ситуаций, конечно-же, нужно удалять слушателей вручную.
В данном примере криминала нет, так как записи гостевой книги с их комментариями удаляются комплексно, то изменение коллекции комментов, после удаления прямых ссылок на экземпляры GuestbookEntry и до уничтожения их сборщиком — невозможно.
> Скучно, старомодно и не практично.
Странно слышать такие слова от опытного разработчика :) Первые два оспаривать смысла нет. По поводу “практично”:
- Практично знать свой код – что когда создается и удаляется. Знать точно. Что оно удаляется :)
- Слабые ссылки необходимы в некоторых случаях, однако описанный Вами таковым не является. Если другой разработчик будет разбираться в этом коде, это может его только запутать. Да и Вас самих через месяц.
- В функции dispose(); можно сделать еще много чего хорошего, что поможет GC освободить ресурсы, если вы использовали что-то кроме get/set comments.
P.S: Товарищи Nox Noctis и Delimeter не так давно однозначно высказали свое обоснованное большими проектами мнение на эту тему – dispose(); форева.
Под практичностью, можно понимать многие другие вещи тоже.
По вашим же словам, выходит, что-то вроде вудуизма — уверенным до конца в способностях Flash Player мы, якобы, быть не можем, по-этому делаем вручную что-бы «знать точно». Я уверен, что это неправильный и непрактичный подход.
Если другой разработчик разберётся в смысле «слабых» ссылок, он никогда не запутается в таком коде. Надеюсь вы понимаете, что это вопрос професионализма в технологии vs. common sense. То-есть, когда мы используем якобы нестандартный синтаксис объявления «слабых» ссылок, мы вырываем читателя кода, из сферы обычного кода, в, якобы, напряжный, о котором нужно думать больше, чем следовало бы.
Это всё нюансы, о которых можна спорить долго и безрезультативно. Тут уж кому как нравится. Мне нравится современный подход, который учитывает в коде существование GC, а не судорожные попытки уничтожать в ручную все объекты, как во времена C. И я уверен, что как раз второй вариант приведёт к большим ошибкам. Так как всех людей на большом проекте нужно будет заставлять писать так, как уже ни на каких других языках не пишут. И при этом, найти аргументы, что-бы убедить их не использовать доступные от Adobe средства для решения подобных проблем.
Лично я не доверяю GC во Flash Player. Да, в некоторых местах нашего проекта ссылки ставятся слабыми, потому как их удаление достаточно трудоемкое. Во всех остальных местах остался принцип «нагадил, поюзал, убери за собой».
И всё бы ничего, но тот факт, что totalMemory нам сообщает о прекрасном (память очистилась), реальное использование памяти Flash Player-ом исключительно растет, несмотря на удаление всего и вся (проверено профайлером). Поэтому доверять на 100% GC я не могу.
Кстати да, я тоже заметил, что GC по неизвестным принципам не очишает такие типы объектов, как, например, String. В какой-то момент, кол-во их в памяти переваливает за десятки тысяч и занимают от 30% всей памяти. Причем, Profiler не показывает объекты, которые могли бы содержать на них ссылки =(.