Many times when we find memory leaks in our applications, it’s due to listeners that have been set by us or someone else and never removed from memory. Although it’s recommended to use weak reference when setting event listeners, the fact that we have to put these three extra parameters often cause developers not to set them, which blocks the garbage collector from collecting these objects:
eventDispatcherObject.addEventListener(type,handler,false,0,true);
The solution is simple. All display objects such as Sprite, UIComponent, Spark etc are all extending event dispatcher class and if you set a line break point and look in the debugger you can see that any display object that the event listeners were set are actually stored in an array.

The only problem is that the array is set to private, which prevents us from cleaning an object from it’s listeners.
See EventDispatcher class:
private function get listeners() : Array;
Yesterday, I placed a CR in JIRA and recommended changing the access modifier in the EventDispatcher class to help prevent memory leaks and increase performance (please vote if you agree):
http://bugs.adobe.com/jira/browse/ASC-4104
I also recommended considering adding a method to EventDispatcher called clearAllListeners(); to allow removing any event listeners that have been created on an object. I am not sure it if makes total sense, since it may add an overhead to any class that extends EventDispatcher. However, having access to listeners() can allow us to remove all listeners, when needed.
With that said, even if Adobe’s SDK team agrees to make the change I highly doubt it if EventDispatcher will end up changing quickly, since it’s part of the core libraries: playerglobal.swc and these are not changing often.
Meanwhile, I created a quick solution to help out with keeping tabs of event listeners added to an object in an easy way.
The idea is to make it simple enough and lightweight so that it will be useful without the need to write any code to create the class or initialize a collection object (such as Array or a Dictionary). Also this class doesn’t have to be used at all times, but be there when you need it and when you have cases where you suspect you’re dealing with a memory leak that is related to an event listener that hasn’t been removed.
Implementation
I will start from the implementation, just to show how easy it is to use. We set an event just like before with one change. We hold a reference to the type and the handler while setting the listener.
movieClip.addEventListener( listeners.type = MouseEvent.CLICK, listeners.handler = onClick ); movieClip.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = onDoubleClick );
As you can see we just set the listeners.type and listeners.handler and it automatically creates a collection that stores references for you, while you add the event listener and then when you want to remove the listeners you just call the following method:
listeners.removeAllListeners( movieClip );
No need to do anything else… that’s it - it’s that simple!
API
Although it’s unconventional I set the class name (listeners) as lower case on purpose, so when you implement it will appear as if it’s part of your class when using the API.
package com.elad.optimize.memory
{
import flash.events.IEventDispatcher;
public class listeners
{
private static var listenerItem:ListenerItem = null;
private static var listenerItems:Vector.<ListenerItem>;
public static function set type( listenerType:String ):void
{
listenerItem = new ListenerItem;
listenerItem.type = listenerType;
}
public static function set handler( eventHandlerReference:Function ):void
{
if (listenerItem == null)
return;
listenerItem.handler = eventHandlerReference;
if ( listenerItems == null )
listenerItems = new Vector.<ListenerItem>();
listenerItems.push( listenerItem );
listenerItem = null;
}
public static function removeAllListeners(eventDispatcherObject:IEventDispatcher, clearListenerItems:Boolean = true ):void
{
if ( listenerItems == null )
return;
var newListenerItems:Vector.<ListenerItem> = new Vector.<ListenerItem>();
listenerItems.forEach( function callback(item:ListenerItem, index:int, vector:Vector.<ListenerItem>):void {
if (eventDispatcherObject.hasEventListener( item.type ) )
{
eventDispatcherObject.removeEventListener( item.type, item.handler );
if ( eventDispatcherObject.willTrigger( item.type ) )
newListenerItems.push( item );
}
else
{
newListenerItems.push( item );
}
});
if (clearListenerItems)
listenerItems = null;
else
listenerItems = newListenerItems;
}
}
}
class ListenerItem {
public var type:String;
public var handler:Function;
}
Each time you set the type a new ListenerItem is created to hold the the type and handler.
public static function set type( listenerType:String ):void
{
listenerItem = new ListenerItem;
listenerItem.type = listenerType;
}
Once both type and handler are set we can add them to the listenerItems collection.
public static function set handler( eventHandlerReference:Function ):void
{
if (listenerItem == null)
return;
listenerItem.handler = eventHandlerReference;
if ( listenerItems == null )
listenerItems = new Vector.<ListenerItem>();
listenerItems.push( listenerItem );
listenerItem = null;
}
When you’re ready to remove all the listeners you can use removeAllListeners method. There may be cases where you want to store the events added on more than one object so when it’s time to clean you can use clearListenerItems set to false, so it will keep the collection of handlers and we will be able to clean other objects.
public static function removeAllListeners(eventDispatcherObject:IEventDispatcher, clearListenerItems:Boolean = true ):void
{
if ( listenerItems == null )
return;
var newListenerItems:Vector.<ListenerItem> = new Vector.<ListenerItem>();
listenerItems.forEach( function callback(item:ListenerItem, index:int, vector:Vector.<ListenerItem>):void {
if (eventDispatcherObject.hasEventListener( item.type ) )
{
eventDispatcherObject.removeEventListener( item.type, item.handler );
if ( eventDispatcherObject.willTrigger( item.type ) )
newListenerItems.push( item );
}
else
{
newListenerItems.push( item );
}
});
if (clearListenerItems)
listenerItems = null;
else
listenerItems = newListenerItems;
}
Example
Using the API can help when you set an anonymous handler. It’s not recommended to set a weak reference to an anonymous handler, since the listener may be removed next time the garbage collector will do a round and it will appear as if our method sometimes is working and sometimes isn’t.
What I usually do is use argument.callee
event.currentTarget.removeEventListener(event.type, arguments.callee);
movieClip.addEventListener( MouseEvent.DOUBLE_CLICK, function(event:*):void {
event.currentTarget.removeEventListener(event.type, arguments.callee);
});
This will remove the listener after the first time the handler got called. However, there are cases when you want to use anonymous method and remove the listener later on. Using the API you can do that by just storing a reference to the method, just as we have done before:
movieClip.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = function(event:*):void {
trace("DOUBLE_CLICK");
});
Now let’s look at the complete example. We set two movie clips with event listeners and then each button allows us to remove a listener from each one of the movie clips. Check method allows us to check the status of the object and see if that event is still set on that object or not.

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="955" minHeight="600"
creationComplete="check()">
<fx:Script>
<![CDATA[
import com.elad.optimize.memory.listeners;
private var movieClip:MovieClip = new MovieClip();
private var movieClip2:MovieClip = new MovieClip();
protected function btn_clickHandler(event:MouseEvent):void
{
movieClip.addEventListener( listeners.type = MouseEvent.CLICK, listeners.handler = onClick );
movieClip2.addEventListener( listeners.type = MouseEvent.CLICK, listeners.handler = onClick );
movieClip.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = function(event:*):void {
trace("DOUBLE_CLICK");
});
movieClip2.addEventListener( listeners.type = MouseEvent.DOUBLE_CLICK, listeners.handler = function(event:*):void {
trace("DOUBLE_CLICK");
});
check();
}
private function onClick(event:MouseEvent):void
{
trace("onClick");
}
private function check():void
{
trace("hasEventListener movieClip CLICK: " + movieClip.hasEventListener( MouseEvent.CLICK ) );
trace("hasEventListener movieClip2 CLICK: " + movieClip2.hasEventListener( MouseEvent.CLICK ) );
trace("hasEventListener movieClip DOUBLE_CLICK: " + movieClip.hasEventListener( MouseEvent.DOUBLE_CLICK ) );
trace("hasEventListener movieClip2 DOUBLE_CLICK: " + movieClip2.hasEventListener( MouseEvent.DOUBLE_CLICK ) );
trace("------------------------------------");
}
]]>
</fx:Script>
<s:Button id="btn" label="Add event listeners"
click="btn_clickHandler(event)"/>
<s:Button label="remove movieClip listeners" x="158" y="0"
click="listeners.removeAllListeners( movieClip, false ); check();" width="171"/>
<s:Button label="remove movieClip2 listeners" x="158" y="29"
click="listeners.removeAllListeners( movieClip2, false ); check();"/>
</s:Application>
Console results:
application complete: hasEventListener movieClip CLICK: false hasEventListener movieClip2 CLICK: false hasEventListener movieClip DOUBLE_CLICK: false hasEventListener movieClip2 DOUBLE_CLICK: false ------------------------------------ Once we click to add the event listeners: hasEventListener movieClip CLICK: true hasEventListener movieClip2 CLICK: true hasEventListener movieClip DOUBLE_CLICK: true hasEventListener movieClip2 DOUBLE_CLICK: true ------------------------------------ Once we clear the movie clip object: hasEventListener movieClip CLICK: false hasEventListener movieClip2 CLICK: true hasEventListener movieClip DOUBLE_CLICK: false hasEventListener movieClip2 DOUBLE_CLICK: true ------------------------------------ Once we clear the second movie clip object: hasEventListener movieClip CLICK: false hasEventListener movieClip2 CLICK: false hasEventListener movieClip DOUBLE_CLICK: false hasEventListener movieClip2 DOUBLE_CLICK: false ------------------------------------
Project:
http://github.com/EladElrom/Flash-Optimizing-Tools
http://github.com/EladElrom/Flash-Optimizing-Tools/blob/master/src/com/elad/optimize/memory/listeners.as
Cheers


























