#include espresso;

An ActionScript Proxy Event Dispatcher

Sunday, 14 September, 2008 Posted in:

There are times when I need to listen to an object’s event for just a single dispatch and then be done with the listener.  When I’m faced with this situation I tend to want to define the event handler as a closure or an anonymous function as opposed to adding yet another class method to handle the dispatch.  A complication with this approach comes from the fact that I also want to explicitly remove my event listeners for the sake of good garbage collection.  So, to have my cake and eat it to I created a proxy class that takes care of listening to the event and cleaning up after itself once the event has been handled.

A Closer Look at the Problem

To better illustrate what I’m describing suppose (for whatever reason) we have an instance of a Loader object and we’re going to listen for its complete event.  We might create a class like this:

class Example {
    rpublic var loader:Loader;
    public function Example() {
        loader = new Loader();
        loader.addEventListener(Event.COMPLETE, onLoaderComplete);
        loader.load();
    }
 
    public function onLoaderComplete(event:Event):void {
        //stuff to do
    }
}

In the example above the Loader is a property of the class and the class has a method that listens to the loader’s complete event and you would probably create something similar to this if your Loader instance needed to last the lifetime of the class.  But, what if it doesn’t?  Suppose your class only needs the Loader instance for a single use and then its done with it. In this case it would be nice to create the Loader only when its needed, let it expire as soon as its done, and to have is event handler as an anonymous function instead of a named method cluttering up the class.   In this situation we might refactor our Example class like so:

public class Example {
    public function Example() {
        var loader:Loader = new Loader();
        loader.addEventListener(Event.COMPLETE, function(event:Event):void {
            //stuff to do
        });
        loader.load();
    }
}

With the above example we have two things to consider, one is garbage collection, and the other is scope.  An event handler declared this way is what is known as a closure and, depending on what tasks the function needs to perform, it may or may not create a problems either garbage collecting the Loader instance and/or the instance of Example, or with scope referencing properties and methods belonging to Example.

A Proxy Class to the Rescue

To address these two concerns we will write another class whose purpose is to maintain the correct scope in the handler function, and that will remove the anonymous function from listening to the loader, thereby keeping the garbage collector happy.

Our proxy needs to know four things: the object that will dispatch the event, the event to listen to, the function to use as an event handler, and the object in whose scope the event handler needs to execute.  Lets start by defining some variables for these four things.  The object who will be dispatching the event, in this case our loader object, can be defined as a type of IEventDispatcher since every class that extends EventDispatcher implements IEventDispatcher. The event can be defined as a string.  The event handler is a type Function and the object defining its scope can simply be referenced as a type Object.  Our class structure should look something like this:

public class ProxyEventHandler {
    protected var _dispatcher:IEventDispatcher;
    protected var _event:String;
    protected var _listener:Object;
    protected var _handler:Function;
}

The next step is to define the class constructor.  It should have arguments representing each of its four properties and set its properties to the values passed in its arguments.

function ProxyEventHander(dispatcher:IEventDispatcher, event:String, listener:Object, handler:Function){
    _dispatcher = dispatcher;
    _event = event;
    _listener = listener;
    _handler = handler;
}

But we’re not done with the constructor yet.  We still need to add an event listener to the dispatcher.  We do not want to use the handler function defined in the constructor’s arguments because it would not allow us to correctly scope the function, nor would it allow us to remove the event listener after the event was dispatched and clean up after ourselves. Instead, we need to define our own callback which will be responsible for these tasks.

Our proxy event handler needs to do the following things: remove itself as an event listener from the dispatcher, execute the handler function that was passed to the constructor in the correct scope, and finally, clean up after itself by setting all of the proxy classes properties to null.  The method will look something similar to this:

protected function callback(e:Event):void {
    _dispatcher.removeEventListener(_event, this.callback);
    _handler.apply(_listener);
    _dispatcher = null;
    _event = null;
    _listener = null;
    _handler = null;
}

Notice the first line of the callback removes itself as an event listner from the dispatcher, while the second line is responsible for calling the actual handler function in the correct scope by calling the handler’s apply method and passing it a reference to the object representing the correct scope.

Now that we have a method defined to act as a proxy for the actual handler method, we can finally add a line to the constructor that establishes the event listener.  Our constructor should now look like this:

function ProxyEventHander(dispatcher:IEventDispatcher, event:String, listener:Object, handler:Function){
    _dispatcher = dispatcher;
    _event = event;
    _listener = listener;
    _handler = handler;
    _dispatcher.addEventListenter(event, this.callback);
}

Wrapping it All Up

With our ProxyEventHandler class defined we can refactor our Example class one last time and use our proxy to handle our loader’s event.

public class Example {
    public function Example() {
        var loader:Loader();
        var peh:ProxyEventHander = new ProxyEventHandler(loader, Event.COMPLETE, this, function(event:Event):void {
            //stuff to do
        });
        loader.load();
    }
}

Both the loader and proxy event handler are instantiated with local scope  so they will persist only so long as they are referenced by another object.The proxy event handler takes care of this by keeping a reference to the loader, and by registering itself as an event listener to the loader.  When the loader dispatches its complete event the references are released, thereby allowing both objects to be garbage collected.  Also, thanks to handler’s apply method, the actual handler method executes in the desired scope, even though it is defined as an anonymous function.

We all know that there is more than one way to code an app, so a tool like the ProxyEventDispatcher may or may not be something you choose to keep in your tool box.  Regardless, I hope it can at least help provide some insight into creative ways of addressing design concerns the next time you find yourself faced with a similar challenge.

Enjoy.

Sample Code

import flash.events.Event;
import flash.events.IEventDispatcher;
/**
* A disposable proxy event handler that goes out of scope once it has received the specified event
* from the specified dispatcher. This class provides a way to listen to and package a response for
* a single event.
*/
public class ProxyEventHandler
{
    protected var _dispatcher:IEventDispatcher;
    protected var _event:String;
    protected var _listener:Object;
    protected var _handler:Function;
    /**
    * Constructor.
    *
    * @param dispatcher The an object implementing the IEventDispatcher interface. This is the object who's event is listened for.
    * @param event The name of the event to listen for.
    * @param listener The object for whom this class is acting as a proxy. The handler function will execute in the listener's scope.
    * @param handler The method on the listener, or an anonymous function that will be called when the event is dispatched.
    *
    */
    public function ProxyEventHandler(dispatcher:IEventDispatcher, event:String, listener:Object, handler:Function)
    {
        _dispatcher = dispatcher;
        _event = event;
        _listener = listener;
        _handler = handler;
        _dispatcher.addEventListener(_event, this.callback);
    }
    /**
    * The callback for the event.
    * @param e
    *
    */
    protected function callback(e:Event):void
    {
        _dispatcher.removeEventListener(_event, this.callback);
        _handler.apply(_listener);
        _dispatcher = null;
        _event = null;
        _listener = null;
        _handler = null;
    }
}
Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay

Add a Comment: