Archive for August, 2007

Never Ever Use Tween or URLLoader or Loader or Timer or…

Wednesday, August 1st, 2007

The Situation

Your Tweens aren't finishing. Or your URLLoaders won't load. Or your Timers stop ticking.

The Problem

Consider the following function:

function doStuff():void
{
	var x:Object = {};
}

When this method is called, it creates an object and stores a reference to it in the variable x. After the function finishes executing, there are no remaining references to the object, so the Flash Player clears it from memory (as soon as it gets around to it). Of course, this happens no matter what type of object we create.

Unfortunately, there are some situations where this behavior can cause major problems. Objects like Tween, Loader, and Timer are associated with asynchronous processes (animating, loading, etc..) that we expect to complete regardless of whether the objects that originated them are still in the player's memory.

Sometimes, we're lucky. Depending on the how many things your application is trying to do at once, the garbage collector might not perform a sweep while our Tweens are playing (or our URLLoaders are loading). Still, it's impossible to anticipate exactly when the garbage collector will do its thing, and we certainly can't allow it to decide whether our applications will function correctly or not. Therefore, we can never ever use these classes without storing a reference to each instance we create.

However, when the number of objects is dynamic (or you're simply making a lot of them), this can get to be a hassle pretty quickly. This problem is illustrated by the following (admittedly outlandish) function, which creates 1000 Sprites and uses 1000 Tweens to fade them out.

function doSomething():void
{
	var mySprite:Sprite;
	for (var i:uint = 0; i < 1000; i++)
	{
		mySprite = new ItemInTheLibrary();
		this.addChild(mySprite);
		mySprite.x = Math.random() * this.stage.stageWidth;
		mySprite.y = Math.random() * this.stage.stageHeight;
		new Tween(mySprite, 'alpha', None.easeNone, 1, 0, 1, true);
	}
}

In its current state, this function just won't work. (At least not on a MacBook Pro with 2GB of RAM.) All of the Sprites will be created and added to the stage, but few (if any) of the Tweens will play. By not storing any references to our Tweens, we authorize the garbage collector to dispose of them as soon as the Flash Player needs the memory for something else. Pushing each Tween instance into an Array would solve our problem (try it), but then we'd have to worry about removing them from the Array when the animation finished (or else leave 1000 unused Tweens hanging around in our computer's memory).

The Fix

In almost every conceivable use of Loader, URLLoader, and Timer, there should be no difficulty in maintaining a reference to your instance. Because you'll rarely need to make many instances of each class at once, storing them in a private property will usually suffice. (Don't forget to null the reference when you're done with it!)

private var _timer:Timer;

private function _startTimer():void
{
	this._myTimer = new Timer(1000, 1);
	this._myTimer.addEventListener(TimerEvent.TIMER, this._timerHandler);
	this._myTimer.start();
}

private function _timerHandler(e:TimerEvent):void
{
	// Remove the listener
	e.currentTarget.removeEventListener(e.type, arguments.callee);
	
	// We're done with the timer. Let it die.
	this._myTimer = null;
}

Tweens are a slightly different beast. Luckily, there are many fine tweening libraries available. It's no coincidence that these libraries usually allow you to create tweens using static methods (for example, Tweener.addTween()); after all, there's no danger in Flash garbage-collecting your instances if you're not dealing with any instances.

If, however, you've got a big project that's already using Adobe's classes (without maintaining references to your instance), you've probably come here in search of The Quick Fix. For that, I'm making available a set of classes (GCSafeAnimator, GCSafeLoader, GCSafeTimer, GCSafeTween, and GCSafeURLLoader) that prevent themselves from being garbage collected while their respective processes are underway. Each class has the exact same API as its Adobe counterpart, so to create the Tween in the above function, for example, you would type:

new GCSafeTween(mySprite, 'alpha', None.easeNone, 1, 0, 1 true);

Similarly, loading a url would look like this:

var ldr:GCSafeURLLoader = new GCSafeURLLoader();
ldr.load(new URLRequest(‘http://exanimo.com'));

To further facilitate search-and-replacing, the packaging is also based on that of the Adobe classes (so GCSafeURLLoader is located at com.exanimo.net.GCSafeURLLoader and GCSafeTween is located at com.exanimo.transitions.GCSafeTween). When an object is no longer performing its task (be it tweening, loading, or whatever), it again becomes eligible for garbage collection.

Download the GCSafe examples and source as a zip file or from my as3 subversion repository (AS3 only). They're available under the MIT License.

Ultimately, having objects that manage their own memory is not the best idea. While these classes fulfill the expectations that you probably had about Adobe's counterparts, I would not recommend using them on new projects. Instead, it's better to understand why your objects were being garbage collected in the first place and take steps to avoid it.