Cleaning up after closures in Flash

19 08 2011

If you haven’t experimented much with closures yet – whether in your Flash/Flex projects, Javascripting or while tinkering with Lua – it’s time to start. In case you’re a little nervous about those pesky memory leaks in Flash, here are some ways to cope.

Pampas Fractal

» 

Much of the following code is bundled into an example Flex project that compares the various closure techniques around a custom Timer class.

Check the code on Github.

What are closures?

Many people think of closures as anonymous functions – probably because that’s the common form they take – but they are more than that. They are scoped, inline functions that provide a “closure” over a collection of free variables (within the function scope).

Check out the Wikipedia entry on closures..

Why use them?

  • They allow the hiding of state (negating the need for maintaining async state in the class) as each closure defines its own variable scope that are available to all nested closures; and
  • Because they’re easier to follow than continually jumping to functions defined at a type-level.

Take a peek

In the below example, we have two closures – the first defines the userEvent property and someVariable, the second adds to that with it’s own scope of serviceEvent. You see how the inner closure has access not only to that state within itself, but also to that of the outer closure, as well as that of the init() function AND the class itself. Welcome to the scope chain. Read the AS3 docs on Function Scope.

public class SomeClass
{
	protected var view:ISomeView;

	public function init():void
	{
		var functionSaysSo:Boolean = true;
	
		userAction.addEventListener(UserEvent.LOGIN, 
			function(userEvent:UserEvent):void
			{
				//this is outer closure 
			
				//define a variable in the outer closure's scope
				var someVariable:String = "something";
			
				service.addEventListener(ServiceEvent.RESULT,
					function(serviceEvent:ServiceEvent):void
					{
						//this is the inner closure

						if (functionSaysSo)
						{	
							view.notify(userEvent.username, serviceEvent.result, someVariable);
						}
					});
				
				//async call
				service.start(userEvent.username, userEvent.pass);
			
			});
	}
}

Tip: If you find yourself contesting the readability assertion from before, don’t fret – it’s early days.

Cleaning up after yourself

Like any listener, using a closure as an event listener can create memory leaks if not properly cleaned up. Luckily, we have a few options up our sleeves to avoid this.

Use weak references? [Short answer: no]

Clean, simple and easy, we could simply add closures as weak references.

userAction.addEventListener(UserEvent.LOGIN, 
	function():void
	{

	}, false, 0, true);	

This keeps the code trim, however it introduces its own problems. If the variable you are listening to lives (is scoped) within another closure or a function definition, it will get cleaned up after the function completes (and before the event might fire). Without a strong reference to that variable, it is a target for garbage collection and you will end up with unpredictable results.

For example, in the following, there is nothing holding onto the timer instance to ensure after the function ends (and before the timer completes) that the timer will still exist and dispatch the TIMER_COMPLETE event.

public class SomethingWeak implements IDoesSomething
{
	public function doSomething():void
	{
		var timer:Timer = new Timer(1000,-1);

		//WARNING! Nothing is holding a reference to timer - GC candidate
		timer.addEventListener(TimerEvent.TIMER_COMPLETE, 
			function(evt:TimerEvent):void
			{
				//something happened!
			}, false, 0, true); 

		timer.start();
	}
}

1. Name your handlers

To improve on this, simply define your handlers locally, and you can remove them within your listeners:

» Aug 30: Updated to inline definition

public class SomethingNamed implements IDoesSomething
{
	public function doSomething():void
	{
		var timer:Timer = new Timer(1000,-1);
		
		var timerHandler:Function;
		
		timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerHandler = function(evt:TimerEvent):void
		{
			//something happened!
			
			//cleanup after ourselves
			timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timerHandler);
		});

		timer.start();
	}
}

2. Use arguments.callee to remove them during execution

Even better, we can take advantage of a little known feature in AS3 called arguments.callee, and not even have to name our function:

public class SomethingCallee implements IDoesSomething
{
	public function doSomething():void
	{
		var timer:Timer = new Timer(1000,-1);
		
		timer.addEventListener(TimerEvent.TIMER_COMPLETE, 
			function(evt:TimerEvent):void
			{
				//something happened

				//cleanup after ourselves
				timer.removeEventListener(TimerEvent.TIMER_COMPLETE, arguments.callee);
			});

		timer.start();
	}
}

3. Use type-level handlers to remove from separate call

Alas, what if you need to clean up based on another method or event later in the piece (say when a mediator is disposed)? You’ll need to define your handler at a type-level to retain a reference of it:

» Aug 30: Updated to inline definition

public class SomethingDisposable implements IDoesSomething, IDisposable
{
	//handler is now defined at a type (class) level
	private var timerHandler:Function;
	
	//we have to also scope the timer to the type level in order to remove listeners
	private var timer:Timer;
	
	public function doSomething():void
	{
		timer = new Timer(1000,-1);
		
		timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerHandler = function(evt:TimerEvent):void
		{
			//something happened!
		});

		timer.start();
	}
	
	public function dispose():void
	{
		if (!timer) return;
		
		timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timerHandler);
		
		//for completeness sake
		timer.stop();
		timer = null;
	}
}

At this point, you may be wondering why bother with a closure, when you could simply define the handler as a private method? In this particular example, there is no difference unless you wanted the handler to access the timer instance itself in the handler.

4. Use Signals

There is a final alternative – use the as3-signals library. AS3 Signals is a library that provides an alternative to using Flash Events within your APIs. Using Signals, there are a handful of alternatives to clean up after your closures. Every signal implements ISignal, and it’s that interface we’ll focus on.

ISignal.addOnce()

ISignal.addOnce() prescribes attaching a handler which is called once when the signal dispatches and is removed immediately. Below we use a NativeSignal to wrap the TimerEvent.TIMER_COMPLETE, allowing us to avoid attaching and removing event listeners ourselves. We also now return a Signal which gives the user of the class a strongly-typed signal to what they expect.

public class SomethingSignalsAddOnce implements IDoesSomethingWithSignals
{
	public function doSomething(index:int):ISignal
	{
		//create a Signal to return
		const response:ISignal = new Signal(int);

		const timer:Timer = new Timer(index * 100,-1);

		//create a signal from the Timer event
		const signal:NativeSignal = new NativeSignal(timer, TimerEvent.TIMER_COMPLETE, TimerEvent);

		//once TIMER COMPLETE has occurred, we can dispatch our signal - ISignal.addOnce() ensures that any listeners to Timer will be cleaned up
		signal.addOnce(function(evt:TimerEvent):void
		{
			//tell response that something happened (as opposed to dispatching an event, we dispatch the signal)
			response.dispatch(index);
		});

		timer.start();

		return response;
	}
}

This is often very useful, but not always optimal. We may not always want to listen only once – say if we need to selectively remove the listener based on certain conditions. Sometimes we may only want to remove the listener based on another asynchronous event (as in #4 above).

Signals and arguments.callee

Alternatively, we could use the arguments.callee property and do a conditional remove when required (after 5 ticks in the below example):

public class SomethingSignalsCallee implements IDoesSomethingWithSignals
{
	
	public function doSomething(index:int):ISignal
	{
		//create a Signal to return
		const response:ISignal = new Signal(int);
		
		const timer:Timer = new Timer(100);
		
		//create a signal from the Timer event
		const signal:NativeSignal = new NativeSignal(timer, TimerEvent.TIMER, TimerEvent);
		
		var numTicks:int = 0;
		
		signal.add(function(evt:TimerEvent):void
		{
			if (numTicks++ == 5)
			{
				response.dispatch(index);
				signal.remove(arguments.callee);
				timer.stop();
			}
		});
		
		timer.start();
		
		return response;
	}
}

You might wonder if you can use arguments.callee within nested closures – and the answer is yes. Just be aware that each closure has its own definition of the arguments.callee, and it overrides the value from any outer closures.

ISignal.removeAll()

ISignal also expose the convenience method: removeAll(). This can help us when we need to remove listeners in response to another method call.

public class SomethingSignalsRemoveAll implements IDoesSomethingWithSignals, IDisposable
{
	private var timerSignal:ISignal;
	private var timer:Timer;
	
	public function doSomething(index:int):ISignal
	{
		//create a Signal to return
		const response:ISignal = new Signal(int);

		timer = new Timer(500);

		//create a signal from the Timer event
		timerSignal = new NativeSignal(timer, TimerEvent.TIMER, TimerEvent);

		timerSignal.add(function():void
		{
			response.dispatch(index);
		});

		timer.start();

		return response;
	}

	public function dispose():void
	{
		timer.stop();
		timerSignal.removeAll();
	}
}

Be careful using removeAll() – if your class aggregates the signal as above, and it never leaves the containing type, fine. However, there may be occasions when you pass a signal around between various classes (as we do with the response signal above). In these classes, using removeAll() could present unwanted results if one developer inadvertently removes listeners that another class attached.

Conclusions

Whichever way you use closures, you need to remember to clean up after yourself, less you end up leaking memory in the Flash player. Asynchronous programming is here to stay (take node.js and Reactive eXtensions for .NET as examples) and we’re lucky that Actionscript – built on ECMA – supports it natively. As long as you’re aware of the consequences of attaching inline handlers, you can use closures and the async model in general to design a different approach to solving common asynchronous problems. While it takes a little getting used to, I wholeheartedly recommend giving it a shot – you might just like it.

About these ads

Actions

Information

11 responses

19 08 2011
Rodrigo P. Fraga ✔ (@digows)

Just another way to the “1. Name your handlers”:

Alert.show(“My Body”, “My Title”, Alert.OK|Alert.CANCEL, null, closeHandler);
function closeHandler( event:CloseEvent ):void
{
if ( event.detail == Alert.OK )
{
//do something…
}
}

19 08 2011
丁光光

Don’t use closure as your event listener. Period.

It’s a bad practice. Every time I review code, question programmer why they use closure as Event listener, and I always can give a more readable, commucative alternative. You can propose any situation that you think closure is suitable, and I can tell you better solution.

We must use closure in javascript because we don’t have choices. But in as3, please recognize the enhancement Adobe has done in these years. It’s a huge evolution. Javascript sucks, but as3 not.

Again, DON’T use closure as your event listener. Period.

19 08 2011
Justin J. Moses

Wow – you are one very jaded developer.

19 08 2011
Thomas Burleson

LOL

19 08 2011
丁光光

Oh, by the way, I don’t think Signals is a good alternative. When talking about messaging, there are far many topics that need to implement: Channeling, Broadcasting, Competing Consumer (think about why we need stopImmediatePropagation), and much more. Signal should be an internal structure of some larger framework, like pureMVC. But use Signal directly, you must discipline your colleagues more.

19 08 2011
Thomas Burleson

Closures [when used correctly] are awesome solutions to async activity, recursion, and iterations. Closures with AS3 are very nice… especially for async handlers. With closures the async handlers are [as Justin states] defined in-context.

But I HIGHLY recommend that developers do not use `anonymous` closures. Nor do I recommend that closures be explicitly assigned to local or class variables.

Instead, simple `name` the closure and reference with the responder. Look at Justin’s “SomethingNamed” example above… then study an alternate [better] use of the same closure: https://gist.github.com/1157828.

Thanks Justin for the heads-up & tricks useful for `forcing` cleanup on closures that will never terminate.

I will agree with 丁光光, however, that closures for background processes that will never end… that is generally a BAD idea.

19 08 2011
Justin J. Moses

@Thomas – what’s your reasoning behind not using anonymous closures?

19 08 2011
Thomas Burleson

@Justin,

Three (3) reasons:

1) Readability: without naming the closure [with a context-specific, understandable name] developers have to infer usage.
2) Scope: sometimes [rare conditions] the scope variables are not accessible; especially if directly add an anonymous function as an eventHandler. If think Colin Moock discusses this in one of his books.
3) Compiler: if you name the function as shown in my Gist sample, the compiler implicitly assigns it as a variable in the parent closure. So the compiler can do the work to maintain reference.
4) Cleanup: with a named function, cleanup can directly reference the `name`.

19 08 2011
Thomas Burleson

I also recommend that developers look at code samples “Cool Techniques with AS3 Closures”. See https://gist.github.com/1017230.

20 08 2011
Jonny Reeves (@jonnyreeves)

Your post made me write up a helper method that will automatically clean up an annoymous function bound to an eventDispatcher, it produces some pretty clean and readable code IMHO:

when(someDispatcher, SomeEvent.SOME_EVENT, function(event : SomeEvent) : void { 
    trace("Heard : + event.type);
});

Code snippet can be see in action over here: http://wonderfl.net/c/8iYU

22 03 2012
internet pro | Pearltrees

[...] Cleaning up after closures in Flash « Justin J. Moses : Blog Many people think of closures as anonymous functions – probably because that’s the common form they take – but they are more than that. They are scoped, inline functions that provide a “closure” over a collection of free variables (within the function scope). Check out the Wikipedia entry on closures. . What are closures? [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




Follow

Get every new post delivered to your Inbox.

%d bloggers like this: