Форум Flasher.ru

Форум Flasher.ru (http://www.flasher.ru/forum/index.php)
-   ActionScript 3.0 (http://www.flasher.ru/forum/forumdisplay.php?f=83)
-   -   После removeEventListener обработчик продолжает вызываться (http://www.flasher.ru/forum/showthread.php?t=195337)

Vreden 05.03.2013 02:55

После removeEventListener обработчик продолжает вызываться
 
Собственно заголовком все сказано, исходник ниже. Из вне создается несколько таких объектов. Я не могу догнать вобще как такое случилось, что после removeEventListener у меня продолжается вызываться обработчик.

обратите внимание на блок с trace("before", death.id) по trace("after", death.id);

Код AS3:

package ***
{
        ***
 
        import flash.display.Bitmap;
        import flash.events.Event;
        import flash.text.TextField;
        import flash.text.TextFieldAutoSize;
        import flash.text.TextFormat;
 
        public class DeathImage extends SpriteControl
        {
                private static const CLOSE_DEATH_BACKGROUND_IMAGE:String = "images/ui/death/closeDeath.png";
                private static const BRONZE_DEATH_BORDER_IMAGE:String = "images/ui/death/bronzeDeathBorder.png";
                private static const SILVER_DEATH_BORDER_IMAGE:String = "images/ui/death/silverDeathBorder.png";
                private static const GOLD_DEATH_BORDER_IMAGE:String = "images/ui/death/goldDeathBorder.png";
                private static const BRONZE_DEATH_NUMBER_BACKGROUND_IMAGE:String = "images/ui/death/bronzeDeathNumberBackground.png";
                private static const SILVER_DEATH_NUMBER_BACKGROUND_IMAGE:String = "images/ui/death/silverDeathNumberBackground.png";
                private static const GOLD_DEATH_NUMBER_BACKGROUND_IMAGE:String = "images/ui/death/goldDeathNumberBackground.png";
 
                private var __width:int = 157;
                private var __height:int = 150;
 
                private var __death:Death;
 
                private var __isLoaded:Boolean = false;
 
                public function DeathImage(controls:Controls)
                {
                        super(controls);
 
                        this.addEventListener(Event.ADDED_TO_STAGE, __addedToStage);
                }
 
                override public function set width(value:Number):void
                {
                        __width = value;
                        super.width = width;
                }
 
                override public function get width():Number
                {
                        return __width;
                }
 
                override public function set height(value:Number):void
                {
                        __height = value;
                        super.height = height;
                }
 
                override public function get height():Number
                {
                        return __height;
                }
 
                public function set death(value:Death):void
                {
                        __death = value;
                }
 
                public function get death():Death
                {
                        return __death;
                }
 
                private function __addedToStage(e:Event):void
                {
                        this.removeEventListener(Event.ADDED_TO_STAGE, __addedToStage);
 
                        if (!__death)
                                return;
 
                        var images:Array = [];
                        switch (death.conquestLevel)
                        {
                                case 0:
                                        images.push(CLOSE_DEATH_BACKGROUND_IMAGE);
                                        break;
                                case 1:
                                        images.push(BRONZE_DEATH_BORDER_IMAGE, BRONZE_DEATH_NUMBER_BACKGROUND_IMAGE);
                                        break;
                                case 2:
                                        images.push(SILVER_DEATH_BORDER_IMAGE, SILVER_DEATH_NUMBER_BACKGROUND_IMAGE);
                                        break;
                                case 3:
                                        images.push(GOLD_DEATH_BORDER_IMAGE, GOLD_DEATH_NUMBER_BACKGROUND_IMAGE);
                                        break;
                        }
 
                        if (death.conquestLevel > 0)
                                images.push(death.url);
                        trace("before", death.id)
                        controls.images.addEventListener(ImagesEvent.IMAGES_LOAD_COMPLETE, __imagesLoadComplete);
                        controls.images.loadImages(images);
                }
 
                private function __imagesLoadComplete(e:ImagesEvent):void
                {
                        /*if (__isLoaded)
                                return;*/

 
                        __isLoaded = true;
                        controls.images.removeEventListener(ImagesEvent.IMAGES_LOAD_COMPLETE, __imagesLoadComplete);
                        trace("after", death.id);
                        var deathImage:Bitmap;
                        var borderImage:Bitmap;
                        var titleImage:Bitmap;
                        switch (death.conquestLevel)
                        {
                                case 0:
                                        var closeDeath:Bitmap = controls.images.getLoadedImage(CLOSE_DEATH_BACKGROUND_IMAGE);
                                        this.addChild(closeDeath);
                                        break;
                                case 1:
                                        borderImage = controls.images.getLoadedImage(BRONZE_DEATH_BORDER_IMAGE);
                                        titleImage = controls.images.getLoadedImage(BRONZE_DEATH_NUMBER_BACKGROUND_IMAGE);
                                        break;
                                case 2:
                                        borderImage = controls.images.getLoadedImage(SILVER_DEATH_BORDER_IMAGE);
                                        titleImage = controls.images.getLoadedImage(SILVER_DEATH_NUMBER_BACKGROUND_IMAGE);
                                        break;
                                case 3:
                                        borderImage = controls.images.getLoadedImage(GOLD_DEATH_BORDER_IMAGE);
                                        titleImage = controls.images.getLoadedImage(GOLD_DEATH_NUMBER_BACKGROUND_IMAGE);
                                        break;
                        }
 
                        if (death.conquestLevel > 0)
                        {
                                deathImage = controls.images.getLoadedImage(death.url);
                                deathImage.smoothing = true;
 
                                var imageSize:int = width - 30;
                                var koef:Number = deathImage.width / imageSize;
                                deathImage.scaleX /= koef;
                                deathImage.scaleY /= koef;
                                deathImage.x = width / 2 - deathImage.width / 2;
                                deathImage.y = height / 2 - deathImage.height / 2;
                                this.addChild(deathImage);
 
                                if (borderImage)
                                        this.addChild(borderImage);
 
                                if (titleImage)
                                {
                                        titleImage.x = width / 2 - titleImage.width / 2;
                                        titleImage.y = 10;
                                        this.addChild(titleImage);
 
                                        var textField:TextField = TextFormats.defaultTextField;
                                        textField.defaultTextFormat = new TextFormat(TextFormats.EMBED_FONT, 12);
                                        textField.text = death.id.toString();
                                        textField.autoSize = TextFieldAutoSize.LEFT;
                                        textField.x = width / 2 - textField.width / 2 - 1;
                                        textField.y = 11;
                                        this.addChild(textField);
                                }
                        }
                }
        }
}

Результаты трейсов:
before 10
before 13
before 99
before 2
before 51
before 127
before 126
before 128
after 10
after 13
after 99
after 2
after 51
after 127
after 126
after 128
after 128
after 126
after 128
after 127
after 126
after 128
after 51
after 127
after 126
after 128
after 2
after 51
after 127
after 126
after 128
after 99
after 2
after 51
after 127
after 126
after 128
after 13
after 99
after 2
after 51
after 127
after 126
after 128

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

AlexLucas 05.03.2013 03:57

Видимо проблема в том что сам объект типа DeathImage несколько раз добавляется на сцену, или добавляется несколько объектов типа DeathImage подряд, и при каждом
Код AS3:

controls.images.loadImages(images);

несколько раз диспатчится ваш ImagesEvent, судя по тому что трэйсов 'after' больше чем 'before'.
Короче по ходу не в этом классе проблема, а в том что добавляет этот самый DeathImages на сцену.

Vreden 05.03.2013 04:11

Да, таких объектов добавляется подряд несколько, и каждый объект подписывается на данное событие и при получении события отписывается.

В итоге что получается, после того как первый объект от события отписался, второй следом подписался, предыдущий тоже оказывается подписанным?

Я что-то запутался, видимо пора спать.

AlexLucas 05.03.2013 13:23

Для каждого объекта в отдельности, при его добавлении на сцену будет срабатывать его же __addedToStage.

Vreden 05.03.2013 13:56

Ну, да, а при чем здесь __addedToStage?

caseyryan 05.03.2013 13:59

Сделайте вот так при добавлении слушателя:
Код AS3:

controls.images.removeEventListener(ImagesEvent.IMAGES_LOAD_COMPLETE, __imagesLoadComplete);
controls.images.addEventListener(ImagesEvent.IMAGES_LOAD_COMPLETE, __imagesLoadComplete);

или так:
Код AS3:

if (!controls.images.hasEventListener(ImagesEvent.IMAGES_LOAD_COMPLETE)) {
  controls.images.addEventListener(ImagesEvent.IMAGES_LOAD_COMPLETE, __imagesLoadComplete);
}

чтобы точно больше одного слушателя не добавлялось, и проверьте

Vreden 05.03.2013 19:36

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

AlexLucas 05.03.2013 20:33

Либо в __imagesLoadComplete ссылка на controls.images это уже ссылка на другой объект и вы не того отписываете, либо вы его опять где-то подписываете на это событие.
В любом случае из этого куска кода не понять.

Vreden 05.03.2013 20:44

Цитата:

Сообщение от AlexLucas (Сообщение 1123741)
либо вы его опять где-то подписываете на это событие.

Да, я опять подписываюсь на событие, но подписывается другой экземпляр класса DeathImage. Этот-то по идее должен быть отписанным.

Вобщем пришлось решать проблему тем, чтобы каждый экземпляр класса юзал отдельный controls.images, а не один и тот же.
Ибо получается, что подпись на событие экземпляра controls.images подписывает также все другие экземпляры DeathImage, которые уже отписались от него. Честно говоря для меня это поведение неожиданность. Я считал что подпись осуществляется в пределах экземпляра класса, к которому принадлежит обработчик. Неужели это не так?

caseyryan 05.03.2013 22:06

Цитата:

Неужели это не так?
Нет, не так. По сути этому экземпляру вообще пофиг в каком классе его подписали. Если экземпляр класса, в котором находится обработчик, будет собран сборщиком мусора, то при прохожении события вылетит ошибка обращения к объекту null

Vreden 06.03.2013 00:11

Цитата:

Сообщение от caseyryan (Сообщение 1123776)
Если экземпляр класса, в котором находится обработчик, будет собран сборщиком мусора, то при прохожении события вылетит ошибка обращения к объекту null

Если экземпляр класса, в котором находится обработчик, будет обработан сборщиком мусора, то он не удалит экземпляр этого класса, так как обработчик является ссылкой на этот экземпляр, поэтому ошибка никакая не вылетит, а если будет установлен флаг weekReference, то GC удалит ссылку и обработчик не будет вызван, а объект будет удален без всяких ошибок.

А вот сам факт, что экземпляру EventDispatcher пофиг на то, в каком экземпляре его подписывают и посылает событие даже в те экземпляры, которые уже отписались от его события весьма странен.

gloomyBrain 06.03.2013 00:38

Цитата:

А вот сам факт, что экземпляру EventDispatcher пофиг на то, в каком экземпляре его подписывают и посылает событие даже в те экземпляры, которые уже отписались от его события весьма странен.
Это не факт, это Ваше неверное предположение.

Vreden 06.03.2013 00:42

Цитата:

Сообщение от gloomyBrain (Сообщение 1123796)
Это не факт, это Ваше неверное предположение.

Возникло такое ощущение, что вы знаете что-то, чего не знаю я, но не за какие коврижки не хотите раскрывать тайну.

gloomyBrain 06.03.2013 00:45

Я знаю только что если Вы от события отписались, то получать Вы его не будете.

Vreden 06.03.2013 01:00

А если я от него отписался, но подписался в другом экземпляре класса, первый не должен продолжать получать событие, так?

gloomyBrain 06.03.2013 01:06

Да, все верно. Попробуйте провести эксперимент - создайте 2 Shape'а и подпишите их на Event.ENTER_FRAME. И отпишите. И потом по очереди подписывайте/отписывайте. Поймать какой именно Shape получил событие можно через свойство события target.
Уверен, что в итоге Вы убедитесь, что после отписки событие перестает обрабатываться.

Vreden 06.03.2013 01:19

Я в этом уверен изначально.
Но только есть разница между экспериментом и исходником выше, в случае с шейпами, я буду подписыватсья на два разных объекта, а в моем случае я подписываюсь к одному и тому же объекту разными объектами. По идее это не должно иметь значения, просто к слову о разнице. Но у меня как-то так получилось. Вобщем никак не могу найти где я накосячил, но тем не менее из примера видно, что после отписи экземпляр продолжает обрабатывать событие, а после отписи конкретно данный экземпляр далее нигде не подписывается на это событие.

Gaen 06.03.2013 02:55

Есть у меня теория по поводу происходящего. Опирается она на то, что controls.images у вас один на всех, и что при локальном тестировании загрузка происходит практически моментально, и поэтому объекты ловят события по несколько раз, прежде чем успевают отписаться.

Ошибка в том, что все экземпляры слушают Event.COMPLETE, но никто не знает, как отличить событие, вызванное загрузкой своих данных, от остальных точно таких же событий. Поэтому на завершение загрузки для одного экземпляра реагируют сразу все.

В итоге происходит следующее:

1. Вы в цикле добавляете на сцену n экземпляров DeathImage. Каждый экземпляр подписывается на Event.COMPLETE у controls.images и запускает загрузку.

2. Завершается загрузка, вызванная 1м экземпляром; controls.images посылает Event.COMPLETE, который ловят ВСЕ экземпляры DeathImage. В очередь исполнения попадают __imagesLoadComplete всех экземпляров.

3. Начинает исполняться __imagesLoadComplete 1го экземпляра. Пока он исполняется, завершаются загрузки, вызванные остальными экземплярами. Каждая завершенная загрузка пуляет Event.COMPLETE, который кладет в очередь вызовов __imagesLoadComplete ВСЕХ ЭКЗЕМПЛЯРОВ КРОМЕ 1го (т.к. первый уже успел отписаться; это подтверждается тем, что "after 10" в трейсах выводится первым и встречается всего 1 раз).
Итого на момент завершения исполнения __imagesLoadComplete 1го экземпляра в очереди вызовов находится n вызовов __imagesLoadComplete для всех экземпляров кроме 1го (по 1 уже было, по n-1 добавилось только что) и n-1 для 1го (1й вызов только что отработал, n-1 добавились)

4. Исполняется по 1 вызову __imagesLoadComplete для каждого экземпляра DeathImage.
После этого все экземпляры отписаны от Event.COMPLETE, но в очереди УЖЕ ЛЕЖАТ оставшиеся n-1 вызовов __imagesLoadComplete для каждого экземпляра.

5. Для каждого экземпляра по очереди исполняются оставшиеся вызовы.

В реальных условиях, когда загрузка происходит не моментально, и вызовы __imagesLoadComplete, вызванные первым Event.COMPLETE, успеют отработать ДО следующего Event.COMPLETE, все экземпляры успеют от него отписаться, и остальные n-1 событий никто не поймает.

Vreden 06.03.2013 04:31

Gaen, да, все именно так и происходит, браво, я что-то подобное подозревал, но сварить все в кучу в голове не получалось, теперь есть четкое объяснение происходящего.

На самом деле картинки я загружаю с удаленного сервера, поэтому в боевых условиях ситуация будет такая же. Проблему решил отдельным экземпляром image для каждого подписывающегося объекта, а соответственно для каждого теперь своя очередь, но статический Dictionary загруженных изображений. Правда у этого подхода есть парочка небольших минусов, но они терпимы по сравнению с тем, что было.

Такую дырень пришлось бы в любом случае закрывать, даже, если картинки грузились бы долго, т.к. в том случае, когда они уже в кеше браузера, им нужно совсем не много времени чтобы перекочевать в Dictionary.

В любом случае, Gaen, большое спасибо, вы помогли мне объяснить эту ситуацию и я теперь не буду мучаться в догадках.


Часовой пояс GMT +4, время: 03:50.

Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.