If you are an experienced ActionScript 3.0 developer, you have probably been programming the below structure many times a day.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com {
    import flash.events.Event;
    import flash.display.Sprite;

    public class MyDisplayObject extends Sprite {
       
        function MyDisplayObject(){
            addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
        }

        private function onAddedToStage(event : Event) : void {
            removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
            // Creation of class members
        }

        private function stageResize_handler(event : Event) : void {
            // Resize code goes here
        }
       
        private function onRemovedFromStage(event : Event) : void {
            removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
            dispose();
        }
       
        private function dispose():void{
            stage.removeEventListener(Event.RESIZE, stageResize_handler);
           
            try{
                // Removal and disposal of class members    
            } catch(e : Error){
                trace(e.message);  
            }
        }
    }
}

The above structure is so common that most developers can recognize it. Eventually I got tired of setting up the same base structure for all my DisplayObjects and started looking around for better ways. I came up with an abstract class called AbstractDisplayObject.

Basically the AbstractDisplayObject is an intermediate layer between your concrete display object and Sprite. All the tedious tasks are managed in the AbstractDisplayObject class including automatic removal of event listeners. Yes you heard right: Automatic removal of event listeners.

The big question

I have not yet figured out, why Adobe didn’t include intrinsic functionality for smoother objects disposal and removal of event listeners. Basically an object is not eligible for garbage collection before all references and event listeners have been completely removed for that object. Until then it’s kept in memory.

Memory leaking

It’s easy to forget the removal of event listeners especially in larger applications and perpetual betas.
And then you have the trouble: Your application is sucking more and more memory from the browser eventually leading to a crash. So I’m just asking:”Why is Daddy not happy?” Well, now you know!

I’m sure that Adobe and the ECMA Script standard can reason for their choices, but frankly, that’s not helping me out right now.

The implementation

Here is an implementation using AbstractDisplayObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com {
    import adtomic.display.AbstractDisplayObject;

    import flash.events.Event;

    public class MyExtendedAbstractDisplayObject extends AbstractDisplayObject {
       
        public function MyExtendedAbstractDisplayObject() {
        }
       
        override protected function onAddedToStage(event : Event):void{
            super.onAddedToStage(event);
            // Creation of class members
        }
       
        override public function dispose():void{
            try{
                // Removal and disposal of class members       
            } catch(e : Error){
                trace(e.message);  
            }
        }
    }
}

The beauty of AbstractDisplayObject is that object disposal happens automatically when the object is removed from the display list. You don’t have to manually remove event listeners added to the object and you don’t have to write all the Event.ADDED_TO_STAGE, Event.REMOVED_FROM_STAGE and Event.RESIZE blocks. If you are not happy with the default behavior you can override it in the concrete class.

Source code

Take a look at the AbstractDisplayObject class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package {
    import flash.display.Sprite;
    import flash.errors.IllegalOperationError;
    import flash.events.Event;
    import flash.utils.Dictionary;
    import flash.utils.getQualifiedClassName;

    public class AbstractDisplayObject extends Sprite implements IDisposable {
       
        private var _dict : Dictionary;
       
        public function AbstractDisplayObject() {
            if(getQualifiedClassName(this) == toString()) throw new IllegalOperationError("Abstract class can only be extended.");
            addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
            addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);
        }
       
        public function dispose() : void {
        }
       
        protected function onStageResize(event : Event) : void {
        }
       
        protected function onAddedToStage(event:Event) : void {
            removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            stage.addEventListener(Event.RESIZE, onStageResize, false, 0, true);
            onStageResize(null);
        }
       
        protected function onRemovedFromStage(event : Event) : void {
            if(stage.hasEventListener(Event.RESIZE)) stage.removeEventListener(Event.RESIZE, onStageResize);
            removeAllEventListeners();
            dispose();
        }
       
        protected function removeAllEventListeners():void{
            if(!_dict) return;
            for(var key : Object in _dict) removeEventListener(_dict[key].type, _dict[key].listener, _dict[key].useCapture);
            _dict = null;
        }
       
        override final public function addEventListener(type : String, listener : Function, useCapture : Boolean = false, priority : int = 0, useWeakReference : Boolean = false) : void{
            if(!hasEventListener(type)){
                super.addEventListener(type, listener, useCapture, priority, useWeakReference);
                if(!_dict) _dict = new Dictionary(true);
                _dict[type] = new EventInfo(type, listener, useCapture);
            }
        }
       
        override final public function removeEventListener(type : String, listener : Function, useCapture : Boolean = false) : void{
            if(hasEventListener(type)){
                super.removeEventListener(type, listener, useCapture); 
                delete _dict[type];
            }
        }
       
        public function get numListeners():uint {
            if(!_dict) return 0;
            var n:uint = 0;
            for(var key : String in _dict) n++;
            return n;
        }
    }
}

class EventInfo{
   
    public var type         : String;
    public var listener     : Function;
    public var useCapture   : Boolean;
   
    function EventInfo(type : String, listener : Function, useCapture : Boolean){
        this.type = type;
        this.listener = listener;
        this.useCapture = useCapture;  
    }
   
    public function equals(eventInfo : EventInfo) : Boolean {
        return this.type == eventInfo.type && this.listener == eventInfo.listener && this.useCapture == eventInfo.useCapture;
    }
}

And let’s not forget the IDisposable interface:

1
2
3
4
5
package  {
    public interface IDisposable{
        function dispose():void
    }
}

I believe that Aaron Clinger (the author of CASAlib) have done something similar.



One Response to “Abstract Display Object”

  1. Thanks for the post Chris. I’m have already copied the AbstractDisplayObject class and it’s working like a charm!

Leave a Reply

You must be logged in to post a comment.



workline