Why timer handlers should almost always use weak references +
Memory leaks can be hard to track down. Here's one case in Flash that might have you stumped.
Did you know that objects listening on active timers are not eligible for garbage collection?
Let's look at a small example. Here's a test application that creates an object every 5 seconds:
public class TestApp extends Sprite
{
private var timer:Timer = new Timer(5000);
private var objectCount:int = 0;
public function TestApp()
{
timer.addEventListener("timer", timerHandler);
timer.start();
}
private function timerHandler(event:Event):void
{
new MyObject(++objectCount);
}
}
Notice how the application doesn't hold a reference to the newly created object. The object should become eligible for garbage collection immediately, shouldn't it?
Now let's look at the MyObject class:
public class MyObject
{
private var timer:Timer = new Timer(5000);
private var id:int = 0;
private var data:Object = null;
public function MyObject(id:int)
{
trace("Creating Object", id);
this.id = id;
// About 16 MB in memory.
data = new BitmapData(2048, 2048);
timer.addEventListener("timer", timerHandler);
timer.start();
}
private function timerHandler(event:Event):void
{
trace("Object", id, "still alive");
}
}
In the constructor it allocates 16 MB of memory and starts its own timer. You'll notice that it does not, in any way, reference any global object (like the stage).
You would have no reason to believe that the above code contains a memory leak. Yet, what happens when you run the test application? An object is created every 5 seconds, allocating memory for its data, but the object never becomes eligible for garbage collection. The Flash Player keeps consuming memory and at some point crashes.
What's going on?
You see, an active timer, such as the one in MyObject, is already
being referenced by a global object internally. That's how the Flash
Player keeps track of timers it needs to update. The timer, in turn,
references the MyObject instance through the event listener set up in
the constructor.
Here's the chain:
[some internal object] => timer => MyObject instance
To break the chain, you can either stop the timer, or remove the event listener.
Of course, if you knew where and when to break the chain, you wouldn't have a memory leak in the first place! So, as a precautionary measure, it's a good idea to consider using a weak reference to your timer event listener.
If we change the one line in the MyObject source to the below, that
fixes the memory leak:
public class MyObject
{
...
public function MyObject(id:int)
{
...
timer.addEventListener("timer", timerHandler, false, 0, true);
timer.start();
}
...
Tada!
Note that the title of this post says you should almost always use weak references for timer event listeners. Why not always? Because there are legitimate use cases for having the timer as the last reference to an object (the object lives solely for the timer). These cases are the exception rather than the norm though. If your timer does something UI-related, chances are very high that you want the timer to die once the object has been taken off the display list.