Delegate class refined
Thursday, 17 February 2005Introduced in the Flash MX 2004 7.2 updater, Macromedia’s Delegate class was a neat solution to having event listeners called in a given scope. Joey Lott improved on this with his Proxy class, allowing you to optionally send additional arguments to your event handler.
Unfortunately, when using either of these classes it is difficult to remove an event listener from an object, because the actual event listener is an anonymous function which calls your target event handler method in the specified scope. If you want to remove such an event listener, you basically have two options, dicussed below.
Option 1: Storing a local reference
If you’re only dealing with a handful of event listeners, you could take the option of manually storing a reference to your ‘delegate’ functions, which you can then later use to remove the event listener.
The example below is a little contrived, but the basic scenario is that you want to listen for the first change in a TextArea component in a MovieClip.
import mx.controls.TextArea; import mx.utils.Delegate; class MMDelegateTest { private var _delegate:Function; private var _textArea:TextArea; public function MMDelegateTest (textArea:TextArea) { _textArea = textArea; _delegate = Delegate.create(this, onTextAreaChange); _textArea.addEventListener("change", _delegate); } private function onTextAreaChange(event:Object) : Void { trace("MMDelegateTest.onTextAreaChange"); _textArea.removeEventListener("change", _delegate); delete _delegate; } }
Here we create the delegate function and store a reference to it in the private _delegate property. We then use the reference when setting up the event listener, and also again in our event handler to remove the event listener after the first call.
This method works both with Macromedia’s Delegate class and Joey’s Proxy class, but can get very hard to manage if you’re dealing with more than a few event listeners. You also have to manually delete the reference to the delegate function when removing the event listener, otherwise the delegate function won’t get garbage collected. Whether this represent a serious concern for you depends on how many delegate functions you’re going to be creating, but memory leakage is never a good thing.
Option 2: Using a temporary object
If storing local references seems like too much of a hassle, you could always store a reference to the delegate function in a temporary object. Then, using Joey’s Proxy class, you could pass this temporary object to the target event handler as one of the extra arguments that you can get up when calling the Proxy.create() method.
import mx.controls.TextArea; import ascb.util.Proxy; class JLDelegateTest { private var _textArea:TextArea; public function JLDelegateTest(textArea:TextArea) { _textArea = textArea; var obj:Object = new Object(); obj.delegate = Proxy.create(this, onTextAreaChange, obj); _textArea.addEventListener("change", obj.delegate); } private function onTextAreaChange(event:Object, obj:Object) : Void { trace("JLDelegateTest.onTextAreaChange"); _textArea.removeEventListener("change", obj.delegate); } }
Here you don’t have to worry about storing local references, because a reference to the delegate function is passed to the event handler method as the delegate property of the obj parameter. You also don’t have to worry about deleting the stored reference to the delegate function, since the temporary object will be marked for garbage collection once the event listener is removed.
However, having to create a temporary object every time I was creating an event listener that I might want to remove in the future was unnecessarily cluttering up my code. Also, I would also have to explain the solution (and exactly why it works) to every developer who works on a piece of my code, so I set about finding a better solution.
A better solution?
In the end, the solution that works for me is simple - just have the delegate function pass a reference to itself as the final argument to the real event handler. Presenting the DynamicFlash Delegate class.
import mx.controls.TextArea; import com.dynamicflash.utils.Delegate; class DFDelegateTest { private var _textArea:TextArea; public function DFDelegateTest(textArea:TextArea) { _textArea = textArea; _textArea.addEventListener("change", Delegate.create(this, onTextAreaChange)); } private function onTextAreaChange(event:Object, delegate:Function) : Void { trace("DFDelegateTest.onTextAreaChange"); _textArea.removeEventListener("change", delegate); } }
So there you have it. No local references, no temporary objects, just plain and simple event listener obliteration. As with Joey’s class, you can specify any number of additional arguments to be sent to the event handler method when it is called, but there is always a final argument sent that contains a reference to the delegate function that can be used to remove the event listener directly.
Event handlers that aren’t interested in the final argument needn’t even declare it as a parameter in their parameter list, but those that are can use that reference to remove the event listener if necessary. As a bonus, you can swap out Macromedia’s Delegate class for this one using simple search & replace as they have the same interface - just replace every instance of ‘mx.utils.Delegate’ in your code with ‘com.dynamicflash.utils.Delegate’ and you’re good to go.








Isn't it safer to use arguments[arguments.length-1] or arguments.pop(); that way
Greg Burch | Friday, 18 February 2005 | 12:02 amIsn’t it safer to use arguments[arguments.length-1] or arguments.pop(); that way you can insure that you never mess with any optional parameters passed, you always shove your data at the end and pop it off the end the user doesn’t have to have any cares.
Greg: This technique doesn't really lend itself to optional arguments
Steve | Friday, 18 February 2005 | 10:12 amGreg: This technique doesn’t really lend itself to optional arguments in the event handler methods. Even using arguments.pop(), if you haven’t specified an optional argument that your event handler method is geared up to handle, the reference to the delegate function will be specified in its place. That is unless I’m missing something here?
Greg: I see what you mean now, but it's all
Steve | Friday, 18 February 2005 | 11:03 amGreg: I see what you mean now, but it’s all about how you handle your optional arguments. I prefer to actually declare all the arguments that my event handler can expect in the parameter list, and test them againt ‘undefined’ to see if they were actually passed or not.
It’s both a blessing and a curse that Flash is loose enough with its compile-time checking to let you leave out arguments for parameters that have actually been declared in the method signature. On the one hand you get strong type checking on optional arguments, but on the other you don’t get a compiler warning if you miss out an argument that you didn’t intend to be optional because with Flash all arguments are treated as optional.
One of the disadvantages of Delegate was not being able
Bob Donderwinkel | Friday, 18 February 2005 | 12:18 pmOne of the disadvantages of Delegate was not being able to pass extra arguments as mentioned, enter Joey Lott’s Proxy class. And this makes a good addition, nice.
Hey excellent, I saw your post on my blog: http://stimpson.flashvacuum.net/mt/archives/2005/01/article_three_a.html Unfortunately I
Bob Donderwinkel | Saturday, 05 March 2005 | 1:47 pmHey excellent, I saw your post on my blog:
http://stimpson.flashvacuum.net/mt/archives/2005/01/article_three_a.html
Unfortunately I deleted it allong with a huge number of spam.. So feel free re-post it of you want, thx :).
Steve, just want to check if my understanding is correct if
Ronnie | Tuesday, 10 May 2005 | 1:24 amSteve, just want to check if my understanding is correct
if you were to have other optional arguments, you will have to declare them in the handler function and likewise, the delegate paramenter will be placed as the last expected argumen of that handler function.
Would this be correct?
Rinnie: That's the way I'd do it because you get
Steve | Tuesday, 10 May 2005 | 7:14 amRinnie: That’s the way I’d do it because you get the benefit of type checking when using them. If you didn’t want to do it that way you could just use the ‘arguments’ array in the event handling function - the last element in this array will be a reference to the delegate.
What's the argument (no, not that kind of argument) for
Morten Barklund | Wednesday, 08 June 2005 | 10:10 amWhat’s the argument (no, not that kind of argument) for sending a reference to the delegate function as the last parameter to the handler function?
From the handler function, the delegate function could just as easily be referenced as arguments.caller. That would be a much nicer approach in my opinion.
Is it just to make it more obvious, that you can find a reference to the invoking function for inexperienced programmers, or…?
– Morten Barklund - Information Architect
Martin: The reference to the delegate function is always sent,
Steve | Wednesday, 08 June 2005 | 7:53 pmMartin: The reference to the delegate function is always sent, whether you want it or not. To use it you can either declare an extra argument in the handler function or use arguments.pop().
While you could use arguments.caller I wanted my code to be a little more explicit. If arguments.caller works for you then you could use that instead.
Where does the anonymous function that is created get placed
Michael Barry | Monday, 15 August 2005 | 2:58 pmWhere does the anonymous function that is created get placed on the actionscript’s scope chain in this Delegate implementation? I have had problems in the past with creating nested anonymous functions that never get garbage collected after they are called. This eventually turns into huge performace hit the longer the application is used. http://timotheegroleau.com/Flash/articles/scope_chain.htm
Is there anyway to retain scope in a handler function and still evade this performance hit?
Micheal: This has been solved with the latest version of
Steve | Saturday, 03 September 2005 | 7:07 pmMicheal: This has been solved with the latest version of the Delegate class. I’ll blog about this when I get time, but for now you can download it from here.
Hi Steve, can you explain how does the latest version
Ronnie | Wednesday, 28 September 2005 | 8:48 pmHi Steve, can you explain how does the latest version actually helps resolve Michael’s concerns about the garbage collection?
Thanks a lot
Ronnie: It's hard to explain if you don't understand the
Steve | Wednesday, 28 September 2005 | 10:31 pmRonnie: It’s hard to explain if you don’t understand the Flash scope chain. If you haven’t already, you might want to read this article first.
It basically removes the circular reference from the activation object in the anonymous function and the anonymous function itself by making sure that the anonymous function doesn’t actually make use of the activation object. Instead the necessary variables are attached to the Function object so it can reference then directly with
arguments.callee.Hi, Very interesting article because it hits right on an area
Mike | Friday, 17 November 2006 | 11:54 pmHi,
Very interesting article because it hits right on an area I’m concerned about in my application. Perhaps you might be able to help me and others with a similar problem with your obvious expertise.
Say I have a video player (FLVPlayback) and a button that loads videos into it from a list of videos using the following function:
Do I need to remove the previous event listener each time the button is hit and a new video is loaded? If I don’t will there be “memory leak” or an accumulation of listeners on my_FLVPlayback?
Seems if this is the case, your class will be a big help. Thanks, Micahael
Michael: There is no need to worry about memory leaks
Steve | Sunday, 19 November 2006 | 3:06 pmMichael: There is no need to worry about memory leaks in this instance. Since your event handler is attached to the FLVPlayback component instance you only need to attach it once, not every time you load a new video into that component.
The memory leaks mentioned in this article and in the comments are made apparent when many Delegates are created. In the earlier version of this class the Delegate function would retain a reference to itself through the activation object at the top of the scope chain, and because there was a reference to it the delegate would never be garbage collected. This was fixed in version 1.0.1.
Nice work! I feel like if you add the reference to
Jesse | Thursday, 07 December 2006 | 1:59 amNice work!
I feel like if you add the reference to the arguments you are essentially polluting the function with extra information. At first glance this doesn’t seem very safe in those instances when you prefer to omit or null argument values.
My personal jgDelegate class was first expanded from the work at createage.com. Since then it has grown to include create, proxy, returns, overwriting, as well as static & dynamic appending. I recently added to the post to include a version after reading this blog post and another.
If you are interested, check it out over at http://www.justgooddesign.com/blog/jgdelegate.htm
If you modify Joey's Proxy class to always send a
tonypee | Friday, 16 March 2007 | 1:47 amIf you modify Joey’s Proxy class to always send a reference to the Proxy function (used to removeEventListener) then you dont need to save a reference to the object anywhere. Making it quite neat. modd:
class com.ascb.util.Proxy {
public static function create(oTarget:Object, fFunction:Function):Function {
NOTE: THE CODE WAS CUT OFF! the first line of
tonypee | Friday, 16 March 2007 | 1:49 amNOTE: THE CODE WAS CUT OFF!
the first line of this function is added var fProxy:Function = function():Void { aParameters[aParameters.length] = fProxy; var aActualParameters:Array = arguments.concat(aParameters); fFunction.apply(oTarget, aActualParameters); };
Am I missing something, or does this only work if
Dru | Friday, 14 September 2007 | 8:18 pmAm I missing something, or does this only work if you want to remove the event listener in an event? What if I want to remove the listener at a completely different time, like in an onUnload? Seems like the only way to handle that is the first option outlined above. I like the idea of simple and elegant solutions, but someone please explain how I’d use this new class with an the-event-listener-removes-the-event example?
house minor apple busy day girl juicy sun all night
whitereddayd | Sunday, 21 September 2008 | 2:46 pmhouse minor apple busy day girl juicy sun all night usa
ibm we australia home yahoo boat girl
juicyland | Sunday, 21 September 2008 | 10:59 pmibm we australia home yahoo boat girl
hi there Steve and allothers, with regards to your code, what
clark | Sunday, 04 January 2009 | 7:27 pmhi there Steve and allothers,
with regards to your code, what about the scenario i have described below, where there are 2 class listeners, which execute functions, however only the one function is supposed to remove both the eventListeners, how will your class handle this? because the way i see it, you have a delegate function parameter passed into the function being called, but now if there are 2 eventListeners to remove what happens there? i sort of tried to do it this method with your code, am not getting it right, and not sure if any of my event listeners are being deleted…
class Apple{ public function Apple() { this.addEventListener("blue", Delegate.create(this, funcA); this.addEventListener("red", Delegate.create(this, funcB); }public function funcB(event:Object) { this.removeEventListener("blue", Delegate.create(this, funcA); this.removeEventListener("red", Delegate.create(this, funcB);
}
}
thanks clarklin