PDA

Просмотр полной версии : Автоматическое удаление listener-ов при удалении объекта


Bomberovich
14.11.2009, 18:16
Доброго времени суток.
Написал простенький класс для вывода мсдж-бокса на экран.


public class MsgBox extends Sprite {

private const MAX_MSGS_IN_QUEUE:uint = 10;
public static const TYPE_OK = 1;
public static const TYPE_YESNO = 2;

public static const OK = 1;
public static const YES = 2;
public static const NO = 3;

private var m_arrMsgs:Vector.<msg_info>;
private var m_arrBtns:Array;
private var m_sprBtns:Sprite;




public function MsgBox() {

m_arrMsgs = new Vector.<msg_info>();
m_sprBtns = new Sprite();
addChild( m_sprBtns );
m_sprBtns.addEventListener( MouseEvent.CLICK, OnBtnClick );
m_arrBtns = new Array();
visible = false;

} // Конструктор



public function Create( msg:msg_info ):Boolean {

if( m_arrMsgs.length >= MAX_MSGS_IN_QUEUE )
return false;

m_arrMsgs.push( msg );
if( !visible ) Show();
return true;

} // Create



private function Show():void {

TfTitle.text = m_arrMsgs[0].title;
TfText.text = m_arrMsgs[0].text;

switch( m_arrMsgs[0].type ) {

case TYPE_OK: {
m_arrBtns[OK] = CreateButton( "Ok", xx, yy );
} break;

case TYPE_YESNO: {
m_arrBtns[YES] = CreateButton( "Да", xx, yy );
m_arrBtns[ NO] = CreateButton( "Нет", xx, yy );
} break;

} // switch


if( !visible ) {
dispatchEvent( new MsgBoxEvent( MsgBoxEvent.SHOW ) );
visible = true;
}

} // Show



public function Hide():Boolean {

RemoveAllButtons();
m_arrMsgs.shift();

if( m_arrMsgs.length == 0 ) {

dispatchEvent( new MsgBoxEvent( MsgBoxEvent.HIDE ) );
visible = false;
return true;

} else {

Show();
return false;
}

} // Hide



public function OnBtnClick( e:MouseEvent ) {

Hide();

} // OnBtnClick



private function CreateButton( sCaption:String, x_pos:Number, y_pos:Number ):Button {

var btn:Button = new Button();
btn.x = x_pos;
btn.y = y_pos;
btn.label = sCaption;
m_sprBtns.addChild( btn );
return btn;

} // CreateButton



private function RemoveAllButtons() {

for( var key in m_arrBtns ) {
m_sprBtns.removeChild( m_arrBtns[key] );
m_arrBtns[key] = null;
}
m_arrBtns = null;
m_arrBtns = new Array();

} // RemoveAllButtons



public function GetButton ( id:uint ) { return m_arrBtns[id]; }
public function GetButtons() { return m_arrBtns; }


Предполагалось использовать всю эту конструкцию следующим образом:
m_MsgBox = new MsgBox();
addChild( m_MsgBox );

// Создаем мсдж-бокс
var Msg:msg_info = new msg_info();
Msg.type = MsgBox.TYPE_YESNO;
Msg.title = 'Вы действительно хотите выйти?';
Msg.text = 'Выход';
m_MsgBox.Create( Msg );

// WeakReference слушатели не помешают кнопке удалиться
m_MsgBox.GetButton( MsgBox.YES ).addEventListener( MouseEvent.CLICK, OnExit , false, 0, true );
m_MsgBox.GetButton( MsgBox.NO ).addEventListener( MouseEvent.CLICK, Continue, false, 0, true );


Т.е. после того, как создается мсдж-бокс, я просто вешаю на его кнопки слушателей.
И вроде бы всё нормально, но слушателей никто не удаляет. Они так и остаются висеть и ловить сообщения несуществующей кнопки, после того, как мсдж-бокс закрыли. К тому же, приходится использовать WeakReference, чтобы кнопки могли удалиться из памяти.

Прошу совета, как это дело лучше перепроектировать. Переопределить addEventListener для Button? Или вообще запретить получение указателей на кнопки MsgBox-а( тогда при создании мсдж-бокса, нужно будет передать указатель на метод, который дернется при mouseEvent.CLICK )? Или дописывать каждый раз удаление слушателя в вызываемый им же метод? А может есть какой-то секрет удаления слушателей при удалении объекта?

Буду рад услышать любые мнения.

random13
14.11.2009, 19:11
ну а что вам мешает сделать что то вроде деструктора, который бы вы запускали перед удалением объектов, а в нем удаляли бы ваши слушатели

Psycho Tiger
14.11.2009, 19:41
REMOVED_FROM_STAGE, думаю.

Bomberovich
14.11.2009, 19:44
ну а что вам мешает сделать что то вроде деструктора, который бы вы запускали перед удалением объектов, а в нем удаляли бы ваши слушатели

Деструктор кнопки? А откуда кнопка узнает, какие на ней слушатели висят?

Felicast
14.11.2009, 21:31
зачем вам давать доступ кому попало к своим кнопкам. никакого GetButton быть не должно.
надо отсылать события самим классом MsgBox. например, OK_CLICKED, CANCEL_CLICKED.
и подписываться к MsgBox, а не к его кнопкам

Bomberovich
14.11.2009, 22:39
зачем вам давать доступ кому попало к своим кнопкам. никакого GetButton быть не должно.
надо отсылать события самим классом MsgBox. например, OK_CLICKED, CANCEL_CLICKED.
и подписываться к MsgBox, а не к его кнопкам

А ничего, что придется переписать все нужные события( PRESS, CLICK, ... ) для всех возможных кнопок? Может быть сделать одно событие MsgBox-а BTN_PRESS, BTN_CLICK,... с полем BtnID, а не для каждой кнопки? Или это те же яйца?

Felicast
15.11.2009, 00:19
А ничего, что придется переписать все нужные события( PRESS, CLICK, ... ) для всех возможных кнопок?
да собственно ничего (вы не заглядывали, сколько флексовые компоненты генерируют событий? и это вполне нормально)
Может быть сделать одно событие MsgBox-а BTN_PRESS, BTN_CLICK,... с полем BtnID, а не для каждой кнопки?
можно. только BtnID - строковое обозначение кнопки (типа MsgBox.YES, MsgBox.OK ...)

GentleFLASH
15.11.2009, 00:25
Bomberovich, а что мешает MsgBox генерировать событие DESTROY или CLOSE и вам подписываться на это событие, где уже ручками можно было бы снять все листенеры?
Ну или хотя бы коллбак организовать, типа onClose

Bomberovich
15.11.2009, 11:59
Felicast
GentleFLASH
Хотелось бы избежать удаления слушателей кнопок(или самого MsgBox-a) внешним классом. Удаляется кнопка - должны удалится все ее слушатели.

Пока в голову приходит только:
class MsgBox {

// Только с помощью этого метода можно будет повесить любого слушателя на любую кнопку
public function addEventListenerToButton( uiBtnID:uint, type:String, listener:Function ) {
m_listeners.push( { uiBtnID:uiBtnID, type:type, listener:listener } );
m_arrBtns[uiBtnID].addEventListener( type, listener, false, 0, true );
} // addEventListenerToButton

// Вызывается при удалении кнопок
private function RemoveAllButtonListeners() {
for( var i:uint=0; i<m_listeners.length; i++ )
m_arrBtns[m_listeners[i].uiBtnID].removeEventListener( m_listeners[i].type, m_listeners[i].listener, false, 0, true );
m_listeners = new Array();
} // RemoveAllButtonListeners

}

etc
15.11.2009, 12:29
Можно переопределить addEventListener у самого MsgBox. И вообще, подписываться только у него на кастомные события типа RESULT_YES, RESULT_NO и т. п.. Когда происходит REMOVED_FROM_STAGE, удалять подписчиков.

Felicast
15.11.2009, 12:31
советую вам задуматься об архитектуре.
кто подписался, тот и должен отписываться.
MsgBox добавляет слушателей к кнопкам. при удалении MsgBox, он отписывается от этих слушателей.
класс, использующий MsgBox, подписывается на события MsgBox (включая MsgBox.CLOSE). при закрытии окна (не важно нажата кнопка YES, NO, CLOSE.....) генерируется событие MsgBox.CLOSE. класс, использующий MsgBox, получает событие MsgBox.CLOSE и отписывается от остальных событий.

SamFR
16.11.2009, 01:18
советую вам задуматься об архитектуре.
кто подписался, тот и должен отписываться.

Ну, это один из путей. В том же самом Qt, например, на нативном уровне организовано автоматическое отписывание всех слушателей объекта (т.е. связанных слотов) при его удалении.

На самом деле, меня всегда тянуло реализовать то же самое на флэше, уж больно удобно. До сих пор удерживает только снижение производительности при переопределении нативного addEventListener(), а ведь это один из наиболее часто используемых методов, и разница даже в миллисекунду будет значить много. Хотя, может быть, не так уж эта производительность и снижается? Нужно протестить, только как-то всё руки не доходят =)

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

Bomberovich
23.11.2009, 22:52
...класс, использующий MsgBox, получает событие MsgBox.CLOSE и отписывается от остальных событий.
В таком случае, кто будет отписывать слушателя события MxgBox.CLOSE? Сам себя?

Всё-таки прихожу к тому, что стоит в данном случае пользоваться дедовскими методами, и при создании мсдж-бокса передавать ему функцию, в которую он отдаст результат. Со слушателями получается много излишнего кода:
1. Подписываемся на событие result_yes
2. Подписываемся на событие close, которое будет удалять слушателя события result_yes
3. в этом событии отписываемся от result_yes и от самого себя
Фактически, перекладывание проблемы с больной головы на здоровую.

SamFR
23.11.2009, 23:28
1. Подписываемся на событие result_yes
2. Подписываемся на событие close, которое будет удалять слушателя события result_yes
3. в этом событии отписываемся от result_yes и от самого себя
Фактически, перекладывание проблемы с больной головы на здоровую.

Немного не так:

Пишем класс MessageBoxEvent, наследующий Event. В нём определяем свойство result, которое будет хранить результат работы диалога, а также публичную константу MessageBoxEvent.DONE типа String, которая будет задавать событие закрытия диалога.
Подписываемся на событие MessageBoxEvent.DONE.
В обработчике этого события отписываемся от него, а также смотрим значение event.result и реагируем соответствующим образом.

Как видите, не настолько уж больше действий.

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

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

Так что у этого метода, как и у любого другого, естественно, имеются и недостатки. Но лично для меня достоинства почти всегда перевешивают :) Если же идти путём сохранения ссылки на функцию, то нужно не забывать, что ссылку эту при удалении окна также придётся занулить.

etc
24.11.2009, 08:24
возможность сохранять дополнительные данные в экземпляре события
Сомнительная фича.

SamFR
24.11.2009, 13:30
Ну не знаю... Раз мы не можем самостоятельно определять сигнатуру функции-обработчика, то почему бы некоторые параметры, напрямую связанные с событием, не передавать в его экземпляре? Я это имел в виду, и не очень понимаю, как это может запутать код или уменьшить его гибкость...

etc
25.11.2009, 00:05
Ну не знаю... Раз мы не можем самостоятельно определять сигнатуру функции-обработчика, то почему бы некоторые параметры, напрямую связанные с событием, не передавать в его экземпляре? Я это имел в виду, и не очень понимаю, как это может запутать код или уменьшить его гибкость...
Излишняя гибкость выливается в то, что у вас абстрактного рода данные передаются. Событию они не нужны, поэтому и не надо их слать.

SamFR
25.11.2009, 00:46
Согласен, что перебащивать с этим не нужно. Но бывают ситуации, в которых, как мне кажется, такого рода данные не будут лишними. Когда у нас имеется узкоспециализированное событие, часто бывает удобным передать о нём дополнительную информацию.

Например, тот же идентификатор нажатой кнопки. Или свойство text в адобовском TextEvent. Тут уже данные совсем не абстрактные, так как класс-обёртка предназначен для конкретного типа события. В некоторых случаях бывает удобным задать два таких поля данных.

Хотя в общем я с вами согласен)

s8000_1
01.05.2010, 04:38
Периодически раз в полгода я начинаю сомневаться.
А зачем вообще делать отписку от событий?
Ведь если объект, испускающий событие, далее не используется (удален с экрана, например), то какие-бы на нем слушатели не висели, он все равно удалится из памяти. Т. к. ссылка на слушатель в испускающем событие объекте, а не наоборот.

Кто-нибудь может привести пример, когда память засоряется и что-то висит в памяти, не удаляясь? Простой, на двух-трех объектах.

По-моему опасна только такого типа ситуация:


public class DynamicViewer extends Sprite
{
public fuction DynamicViewer (model:EventDispatcher)
{
model.addEventListener("someEvent", this._listener);
}
private function _listener(event:Event):void
{
//
}
}

когда слушатель - это метод удаляемого объекта. Да и эта ситуация решается методом проставления слабой ссылки.

Psycho Tiger
01.05.2010, 19:40
Подпишитесь на ENTER_FRAME с трейсом и удалите всё. Трейс будет идти.

Когда объект управляет сам собой - нестрашно. Но когда, например, событие вызывает другое событие, это событие могут поймать наверху и будет кранты.

Или изменение конфигов по событию из сокета, например... в этом примере, конечно, архитектура явно гавно, если визуальный объект (причем не главный) управляет конфигами, но что-то в голову не лезут нормальные примеры.

s8000_1
02.05.2010, 01:12
>Подпишитесь на ENTER_FRAME с трейсом и удалите всё. Трейс будет идти.

Не ну понятно. Трейс будет мешать, производительность падать, но до первого вызова GC. Интересуют именно ситуации, когда объекты в памяти застревают.

В том-то и дело, что когда событие ловит кто-то наверху, это не страшно. Объект не удаляется из памяти только тогда, когда его метод является слушателем неудаляемого. Но слабые ссылки решают этот вопрос... (не поленился проверил профилировщиком разные варианты).

gloomyBrain
02.05.2010, 03:00
Интересуют именно ситуации, когда объекты в памяти застревают.
Могут задержаться, а застрять - нет. Есть же какой-то предел, до которого GC не удаляет объекты из памяти.
На мой взгляд, самостоятельная отписка - это скорее вопрос красоты и стабильности решения, вы просто не оставляете программе вариантов сделать что-то непредсказуемое.
В том-то и дело, что когда событие ловит кто-то наверху, это не страшно.
Вот это как раз неприятно. Ибо, если обработчик не завязан на event.target, то мы получаем действия, которых быть не должно. Хотя, если честно, я больше времени потрачу на придумывание ситуации, когда такое может происходить, чем на написание кода, в котором такого происходить не может в принципе =)

Psycho Tiger
02.05.2010, 19:15
Ну в последнем проекте надо было срочно нарастить на проект фишку, что когда тыркают на кнопку происходит действие, ждем ответа от сокета и выводим на экран алерт через статичный метод. После тыка на кнопку ничего другого, кроме как нужного ответа придти не может. Забыл отписаться. Полезли RTE. Очень неприятно.

dimarik
03.05.2010, 22:02
Доброго времени суток.
Написал простенький класс для вывода мсдж-бокса на экран.

Я вам поздравляю. Што есть мсжд? Про события почитал. Все забавно, но не получится.