ActionScript 3.0 Event Listener Delegates

After some research on C++ delegates, I’ve also tried to build delegates in ActionScript 3.0. It’s extremely easy compared to C++, which is not a surprise at all!

Conveniently, when you refer to a method through an object reference with the “dot (.) syntax”, the evaluated Function object reference already acts somewhat like a delegate, in that the value used for the this keyword is stored within the Function object. This saves us a lot of work!

We can make use of this convenient feature to create “event listener delegates” to pass in extra arguments to listener functions without having to use multiple listeners or using the Dictionary class.

Here I’ll show you how we would solve the same problem using multiple listeners, dictionaries, and finally, event listener delegates.

The Multiple-Listener Approach

Suppose we have three display objects called obj1, obj2, and obj3. We’d like to trace out 1, 2, and 3, when we click the display objects, respectively.

This is the most naive and straightforward approach. You’d just create a different listener function for each of the display object.

function listener1(e:Event):void { trace(1); }
function listener2(e:Event):void { trace(2); }
function listener3(e:Event):void { trace(3); }

obj1.addEventListener(MouseEvent.CLICK, listener1);
obj2.addEventListener(MouseEvent.CLICK, listener2);
obj3.addEventListener(MouseEvent.CLICK, listener3);

So what’s wrong with this approach? Imagine you have 10000 objects and you’re adding listeners using a loop and the associative array syntax:

for (var i:int = 0; i < 10000; ++i)
{
  this["obj" + i].addEventListener
  (
    MouseEvent.CLICK, 
    this["listener" + i]
  );
}

In order for this code to work, you’ll have to declare 10000 different functions!

The Dictionary Approach

It’s clear that we could solve the problem if we can associate a value with each display object. That’s it! We can use the Dictionary class to do that!

var mapping:Dictionary = new Dictionary();

function listener(e:Event):void
{
  //look up our dispatcher
  trace(mapping[e.target]);
}

for (var i:int = 0; i < 10000; ++i)
{
  var obj:DisplayObject = this["obj" + i];
  
  //associate the display object with an integer
  mapping[obj] = i;
  
  //just use the only listener and we'll be fine
  obj.addEventListener(MouseEvent.CLICK, listener);
}

There we have it. We no longer need to declare 10000 different listeners. But the code is still not perfect: dictionaries use the binary search algorithm to look up mappings, which is not a constant time operation. In other words, as the number of dictionary entries increases, it takes longer to look up each entry.

This leads to our final solution to this problem, delegates.

The Delegate Approach

A delegate is an object that acts as an indirect agent between the function being invoked (callee) and the code that invokes the function (caller). We can design a Delegate class that contains a Fucntion reference to the callee and a custom array of arguments to be passed when invoking the callee. In this case, the callee is the trace function and the arguments are the different integers.

class Delegate
{
  private var func_:Function;
  private var args_:Array;
  
  public function Delegate(func:Function, ...args)
  {
    func_= func;
    args_= args;
  }
  
  public function invoke(e:Event):void
  {
    func_.apply(null, args_);
  }
}

This gives us the following client code:

for (var i:int = 0; i < 10000; ++i)
{
  this["obj" + i].addEventListener
  (
    MouseEvent.CLICK, 
    new Delegate(trace, i).invoke
  );
}

We basically “wrap” a Function object reference and the arguments into a Delegate object, and use the delegate’s invoke method as the “indirect agent”. This approach is constant-time, because each call to the delegate involves exactly one indirect function call.

To make the code even cleaner (getting rid of “.invoke” in the code), we can create a helper function:

function delegate(func:Function, ...params):Function
{
  return new Delegate(func, params).invoke;
}

This gives us our final client code:

for (var i:int = 0; i < 10000; ++i)
{
  this["obj" + i].addEventListener
  (
    MouseEvent.CLICK, 
    delegate(trace, i)
  );
}

That’s it! Way simpler than C++ delegates 🙂

About Allen Chou

Physics / Graphics / Procedural Animation / Visuals
This entry was posted in ActionScript. Bookmark the permalink.

3 Responses to ActionScript 3.0 Event Listener Delegates

  1. udaaff says:

    Hi
    It was very usful in AS2 🙂
    I’d use something like that:
    public static function create(o:Object, f:Function, … args):Function
    {
    return function():* { return f.apply(o, args); };
    }

    super.stage.addEventListener(MouseEvent.CLICK, Delegate.create(null, trace, “asdf”));
    super.stage.addEventListener(MouseEvent.CLICK, Delegate.create(null, trace, “qwe”));

  2. milkmidi says:

    沒有讚可以按

Leave a Reply to udaaff Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.