Passing arguments to Flex event handlers using closures
Today, with help from my new friend Jack Kennedy (in San Antonio) and various online sources, I learned how to use "closures" to pass arguments to event handlers in Flex. I remember learning about function closures in a abstract way when studying for my Computer Science degree back in the mid-1980's, but I'd never had the opportunity/need to use them professionally. They weren't supported by any of the languages I was using then, but they are more widely supported now. They are supported in ActionScript 3.0.
Here's the problem I faced this week. I wanted to use a Flex timer to drive an animation, so I created a Timer object and registered an event listener function on it. The listener callback would be called whenever the timer dispatched a TIMER or TIMER_COMPLETE message. But...the callback function needed to access information that was not available within the scope of the callback function. I could put that information into a global variable, but that would smell bad. There did not seem to be a way to hook Timer to dispatch a custom subclass of TimerEvent, which was my first thought -- and my local info wouldn't be available to Timer's TimerEvent factory, anyway. I was stumped.
I recalled that Jack had mentioned, over dinner recently, that he used Timers frequently (and Flash animation rarely), so I figured that he must know how to overcome this problem. Between his cogent, concise response to my email inquiry, and some research to help me understand what closures were and how to use them correctly, I got my code working properly.
First, though, I put together this simple test app to experiment with:
Press the "Draw Circles" button, and the code draws a bunch of randomly-sized circles. The first circle is red, with subsequent circles increasing in hue through a cyclic rainbow until reaching red again, at which point all of the circles are erased. Clearly, the callback function "knows something" about color, else it could not progress smoothly through the rainbow as it does. How is the "Draw Circles" button's click handler providing that state information to the TIMER event callback function?
The answer: through the use of a closure. See the code here.
All of the magic happens in drawCircleHandler(). Here's the code from inside of drawCircleHandler() that registers the callback function for TIMER events:
// pass args to TIMER event handler using a "closure"
var count:Number = 0;
t.addEventListener(TimerEvent.TIMER,
function TimerHandler():void {
// Every time TimerHandler() is called, it increments
// drawCircleHandler()'s local variable "count"
count += DEGREES_IN_CIRCLE/MAX_CIRCLES;
drawColoredCircleInComponent(degreesToRGB(count), cCanvas);
});
(Clearly, I need to figure out how to embed pretty-printed source code in these blog posts.)
The essential point is that TimerHandler(), being defined within the body of drawCircleHandler(), can access drawCircleHandler()'s local variables, such as "count". Every time TimerHandler() is called by Flex's event-dispatching code, it increments the value of "count". This is rather magical, since drawCircleHandler() will long since have completed its execution by then, so one might think that its local (stack frame) variables would have been liquidated. But apparently, the closure's environment is allocated dynamically (from the heap) and retained (i.e., not garbage-collected) so long as anything points at it...anything, including as a Timer's listener-list.
You'll note that "count" is incremented in TimerHandler(). That wasn't my first design. First, I passed "count" into drawColoredCircleInComponent(), in which I incremented it and calculated its RGB equivalent. I figured that since "count" was a Number and therefore an Object, it was being passed by reference, so anything I did to "count" inside of drawColoredCircleInComponent() would act on the local "count" in drawCircleHandler(). Not so; the value of "count" stayed zero -- its initial value. Bummer.
In the second design, I incremented "count" in TimerHandler() and then passed it into drawColoredCircleInComponent(), where its RGB equivalent was calculated. This worked, but it didn't smell right -- why should drawColoredCircleInComponent() know about both "count" and color? My first design had the same smell, once I thought about it. It smelled better -- to my nose, at least -- to have TimerHandler() do handle both incrementing "count" and the calculation of its RGB equivalent, thereby localizing the semantics of "count" within the scope of drawCircleHandler().
That's my story, anyway, and I'm sticking to it. ;-)
Thanks, Jack! :-)
Here's the problem I faced this week. I wanted to use a Flex timer to drive an animation, so I created a Timer object and registered an event listener function on it. The listener callback would be called whenever the timer dispatched a TIMER or TIMER_COMPLETE message. But...the callback function needed to access information that was not available within the scope of the callback function. I could put that information into a global variable, but that would smell bad. There did not seem to be a way to hook Timer to dispatch a custom subclass of TimerEvent, which was my first thought -- and my local info wouldn't be available to Timer's TimerEvent factory, anyway. I was stumped.
I recalled that Jack had mentioned, over dinner recently, that he used Timers frequently (and Flash animation rarely), so I figured that he must know how to overcome this problem. Between his cogent, concise response to my email inquiry, and some research to help me understand what closures were and how to use them correctly, I got my code working properly.
First, though, I put together this simple test app to experiment with:
Press the "Draw Circles" button, and the code draws a bunch of randomly-sized circles. The first circle is red, with subsequent circles increasing in hue through a cyclic rainbow until reaching red again, at which point all of the circles are erased. Clearly, the callback function "knows something" about color, else it could not progress smoothly through the rainbow as it does. How is the "Draw Circles" button's click handler providing that state information to the TIMER event callback function?
The answer: through the use of a closure. See the code here.
www.igetitmusic.com/blog/SWFs/ClosureTest/srcview/index.html
All of the magic happens in drawCircleHandler(). Here's the code from inside of drawCircleHandler() that registers the callback function for TIMER events:
// pass args to TIMER event handler using a "closure"
var count:Number = 0;
t.addEventListener(TimerEvent.TIMER,
function TimerHandler():void {
// Every time TimerHandler() is called, it increments
// drawCircleHandler()'s local variable "count"
count += DEGREES_IN_CIRCLE/MAX_CIRCLES;
drawColoredCircleInComponent(degreesToRGB(count), cCanvas);
});
(Clearly, I need to figure out how to embed pretty-printed source code in these blog posts.)
The essential point is that TimerHandler(), being defined within the body of drawCircleHandler(), can access drawCircleHandler()'s local variables, such as "count". Every time TimerHandler() is called by Flex's event-dispatching code, it increments the value of "count". This is rather magical, since drawCircleHandler() will long since have completed its execution by then, so one might think that its local (stack frame) variables would have been liquidated. But apparently, the closure's environment is allocated dynamically (from the heap) and retained (i.e., not garbage-collected) so long as anything points at it...anything, including as a Timer's listener-list.
You'll note that "count" is incremented in TimerHandler(). That wasn't my first design. First, I passed "count" into drawColoredCircleInComponent(), in which I incremented it and calculated its RGB equivalent. I figured that since "count" was a Number and therefore an Object, it was being passed by reference, so anything I did to "count" inside of drawColoredCircleInComponent() would act on the local "count" in drawCircleHandler(). Not so; the value of "count" stayed zero -- its initial value. Bummer.
In the second design, I incremented "count" in TimerHandler() and then passed it into drawColoredCircleInComponent(), where its RGB equivalent was calculated. This worked, but it didn't smell right -- why should drawColoredCircleInComponent() know about both "count" and color? My first design had the same smell, once I thought about it. It smelled better -- to my nose, at least -- to have TimerHandler() do handle both incrementing "count" and the calculation of its RGB equivalent, thereby localizing the semantics of "count" within the scope of drawCircleHandler().
That's my story, anyway, and I'm sticking to it. ;-)
Thanks, Jack! :-)
Labels: ActionScript 3, AS3, Flex 3, programming


2 Comments:
Hey check out the "asyncHandler" method in the Fluint Actionscript project, they have a nice way of doing these types of custom event handlers.
Cheers,
Lance
Yes, that's a good alternative approach, too. Its code is described here: http://code.google.com/p/fluint/wiki/AsyncTest
The method shown there uses a "passThroughData" argument, typed as Object, to communicate application-specific information from caller to receiver. This approach is WAY better than using a global variable, and has a long history, so it's a good technique to be aware of. Indeed, it's the aproach that I expected Flex to use in its event handler code.
Now that know how to use function closures, I prefer to use them, rather than passThroughData-like objects, to pass arguments to event handlers.
I'd like to give a crisp, rigorous answer as to WHY I like them better, but the best I can think of right now is that using a closure, I can just define the variables I need as local variables within the caller's stack and pass them as arguments to the callee function, without sticking them in an intermediate object along the way.
However, I recognize that I could simply be infatuated with a newly-learned technique. ;-)
So, we're both right! Thanks for your comment, lankyg411! :-)
Post a Comment
Links to this post:
Create a Link
<< Home