Пример использования «слабых» ссылок

Posted by - February 25, 2009

Тема «слабых» ссылок при установлении функций-слушателей (англ. 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 и до уничтожения их сборщиком — невозможно.

4 Comments on Пример использования «слабых» ссылок

Respond | Trackback

  1. > Скучно, старомодно и не практично.

    Странно слышать такие слова от опытного разработчика :) Первые два оспаривать смысла нет. По поводу “практично”:
    - Практично знать свой код – что когда создается и удаляется. Знать точно. Что оно удаляется :)
    - Слабые ссылки необходимы в некоторых случаях, однако описанный Вами таковым не является. Если другой разработчик будет разбираться в этом коде, это может его только запутать. Да и Вас самих через месяц.
    - В функции dispose(); можно сделать еще много чего хорошего, что поможет GC освободить ресурсы, если вы использовали что-то кроме get/set comments.

    P.S: Товарищи Nox Noctis и Delimeter не так давно однозначно высказали свое обоснованное большими проектами мнение на эту тему – dispose(); форева.

    • tearaway_Tea says:

      Под практичностью, можно понимать многие другие вещи тоже.

      По вашим же словам, выходит, что-то вроде вудуизма — уверенным до конца в способностях Flash Player мы, якобы, быть не можем, по-этому делаем вручную что-бы «знать точно». Я уверен, что это неправильный и непрактичный подход.

      Если другой разработчик разберётся в смысле «слабых» ссылок, он никогда не запутается в таком коде. Надеюсь вы понимаете, что это вопрос професионализма в технологии vs. common sense. То-есть, когда мы используем якобы нестандартный синтаксис объявления «слабых» ссылок, мы вырываем читателя кода, из сферы обычного кода, в, якобы, напряжный, о котором нужно думать больше, чем следовало бы.

      Это всё нюансы, о которых можна спорить долго и безрезультативно. Тут уж кому как нравится. Мне нравится современный подход, который учитывает в коде существование GC, а не судорожные попытки уничтожать в ручную все объекты, как во времена C. И я уверен, что как раз второй вариант приведёт к большим ошибкам. Так как всех людей на большом проекте нужно будет заставлять писать так, как уже ни на каких других языках не пишут. И при этом, найти аргументы, что-бы убедить их не использовать доступные от Adobe средства для решения подобных проблем.

      • etc says:

        Лично я не доверяю GC во Flash Player. Да, в некоторых местах нашего проекта ссылки ставятся слабыми, потому как их удаление достаточно трудоемкое. Во всех остальных местах остался принцип «нагадил, поюзал, убери за собой».

        И всё бы ничего, но тот факт, что totalMemory нам сообщает о прекрасном (память очистилась), реальное использование памяти Flash Player-ом исключительно растет, несмотря на удаление всего и вся (проверено профайлером). Поэтому доверять на 100% GC я не могу.

        • tearaway_Tea says:

          Кстати да, я тоже заметил, что GC по неизвестным принципам не очишает такие типы объектов, как, например, String. В какой-то момент, кол-во их в памяти переваливает за десятки тысяч и занимают от 30% всей памяти. Причем, Profiler не показывает объекты, которые могли бы содержать на них ссылки =(.

Respond

Comments

Comments