Packages are actually namespaces in ActionScript 3

by Josh Tynjala

Have you ever looked at the returned value of the function flash.utils.getQualifiedClassName() and wondered why it includes :: between the package and the class name? For example, if you pass in a MouseEvent instance, you'll get the string value "flash.events::MouseEvent". The first time I tried it, I expected to receive "flash.events.MouseEvent" with a . because that's how you reference a packaged class in an import statement. I always thought the difference was a little strange, but I just shrugged and went about my business. Today, though, my mind must have been feeling a little extra clever than ususal because it suddenly dawned on me: you use :: as an operator in ActionScript 3 to access a namespace.

With one simple test, it became unquestionably clear that packages in ActionScript 3 are really an abstraction built on top of namespaces. Try running the following code using the Flex SDK or the Flash authoring tool:

namespace flash_events = "flash.events";
trace( flash_events::MouseEvent ); //output: [class MouseEvent]

No import needed!

I imagine this has ECMAScript 4 roots. In general, many of the OOP features of AS3, like class and package syntax, are considered syntactic sugar. How can AS3 developers benefit from this knowledge? I have no idea, but it's one of those discoveries that whispers, "someday, you may need me".

Get the class used to create an object instance in AS3

by Josh Tynjala

Sometimes, you want to know the datatype of an object at runtime. The most common thing to do in ActionScript 3 is use the is keyword to see if an object is an instance of a specific class. For example, you use exampleObject is Sprite to see if something is a Sprite (or any subclass of Sprite). However, there are times when you want to use the actual class in your code to create a new instance, or to do something a little more arcane. There are a couple simple ways to access the datatype of an object and place it in a variable.

Method 1: Combine a couple flash.utils functions

The first way to get an object’s class at runtime takes requires calls to a couple useful functions that live in the flash.utils package:

import flash.utils.getQualifiedClassName;
import flash.utils.getDefinitionByName;

var example:Sprite = new Sprite();
var exampleName:String = getQualifiedClassName( example );
var exampleType:Class = getDefinitionByName( exampleName ) as Class;
trace( exampleType == Sprite ); //output: true

Start by calling the function flash.utils.getQualifiedClassName(), and pass in the object. It will return a string that includes the full class name, including the package. For example, passing in a Sprite object will return the String value "flash.display::Sprite". Next, call the function flash.utils.getDefinitionByName(), and pass in the String value that you just received. It will return a reference to the object’s class. An equality check is included in the code above to demonstrate that exampleType is actually the Sprite class.

Mainly, I’ve included this first method of extracting a class from an instance because the two utility functions used in the example above are quite useful in other contexts. However, the second method is much simpler, if a little more obscure.

Method 2: The constructor property

Every object in ActionScript 3 has a property named constructor. According to the documentation, it is a reference to the datatype used to instantiate the object. In other words, every object can tell you the Class (or Function, since AS3 still supports ECMAScript function prototypes) used to create it with one simple property lookup. How simple!

var example:Sprite = new Sprite();
var exampleType:Class = Object( example ).constructor;
trace( exampleType == Sprite); //output: true

What’s interesting, and not immediately obvious (unless you read the documentation thoroughly), is that if you try to access the constructor property in the strict mode (which is on by default in the AS3 compiler), you’ll get a compiler error. However, the documentation notes that dynamic objects don’t cause this error. We can use that fact to create an easy workaround because everything is an Object, and Object happens to be a dynamic class. As you can see in the code above, a simple cast makes the error go away.

Personally, I prefer the second method because it takes only a simple property lookup, and it requires no calls to functions with long names. Again, though, those functions can be quite useful in other contexts, so it’s worth making note of them.

Combine buttonMode = true and a mouse click to leak memory

by Josh Tynjala

Here’s a troublesome Flash Player bug I discovered when I couldn’t understand why my SWF wouldn’t unload properly from a parent SWF. Everything seemed to unload fine unless I clicked on any buttons in my game’s menus. How strange. I loaded my SWF up in the Flex Builder profiler and watched what stuck around. Sure enough, in addition to the main document class and anything referenced there, some button instances that should be gone seemed to be sticking around. The reason they were stuck in memory? I set buttonMode to true.

Sounds weird, right? At first, following my own recent advice on how to cleanup an unloading SWF, I started commenting out certain mouse events used by the buttons. My thinking was that maybe these event listeners were causing the stage to keep a reference to the button. Unfortunately, that didn’t seem to help. In frustration, I started commenting out large portions of the button class. With almost no code left to run, I got the SWF to unload properly. No instances of any class in my SWF were left this time, so it was clear that this button class was the culprit. I began slowly adding bits of functionality back in. I eventually discovered two functions that would cause the button to stay in memory, and both had one thing in common: they modified buttonMode. I re-enabled all my code in that class, except for the lines that set buttonMode, and the SWF began unloading perfectly.

That’s a pretty complex case that involves multiple SWFs, and it certainly wasn’t easy to debug. For my bug report to Adobe, I built a very simple test case that you can look at below. I simply creates two Sprite instances that are clickable, one with buttonMode set to true, and one without:

package
{
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.system.System;

	public class ButtonModeBug extends Sprite
	{
		public function ButtonModeBug()
		{
			var button1:Sprite = new Sprite();
			button1.graphics.beginFill(0x009900);
			button1.graphics.drawRect(0, 0, 120, 20);
			button1.graphics.endFill();
			button1.x = 10;
			button1.y = 50;
			button1.addEventListener(MouseEvent.CLICK, regularSpriteClickHandler);
			this.addChild(button1);

			var button2:Sprite = new Sprite();
			button2.graphics.beginFill(0x990000);
			button2.graphics.drawRect(0, 0, 120, 20);
			button2.graphics.endFill();
			button2.x = 150;
			button2.y = 50;
			button2.buttonMode = true;
			button2.addEventListener(MouseEvent.CLICK, buttonModeSpriteClickHandler);
			this.addChild(button2);
		}

		private function regularSpriteClickHandler(event:MouseEvent):void
		{
			var button1:Sprite = Sprite(event.currentTarget);
			button1.removeEventListener(MouseEvent.CLICK, regularSpriteClickHandler);
			this.removeChild(button1);
			button1 = null;
			System.gc();
		}

		private function buttonModeSpriteClickHandler(event:MouseEvent):void
		{
			var button2:Sprite = Sprite(event.currentTarget);
			button2.removeEventListener(MouseEvent.CLICK, buttonModeSpriteClickHandler);
			this.removeChild(button2);
			button2 = null;
			System.gc();
		}
	}
}

Notice that when you click either button, it is removed from the display list, the event handler is removed, the variable is set to null, and then System.gc() is called to force the garbage collector to run.

To see the bug in action, you should run that class in the profiler with live memory data displayed (you may need to unfilter the flash.* classes too). When you click button1 (the green one), you can see the number of Sprite instances decrease by one. When you click button2 (the red one), the number of Sprite instances stays the same.

As a control case, comment out the line that says button2.buttonMode = true;, and run again. Both buttons will be removed from memory, and the number of Sprite instances will decrease to zero.

The description of this bug can be summarized like this:

If you set buttonMode to true on a Sprite, and that Sprite gets clicked by the user, it will not be removed from memory after all references have been removed in ActionScript.

It appears, although I cannot be certain, that Flash Player’s internal code isn’t properly releasing its own reference to the Sprite. Maybe related to the hand cursor? Whatever the case may be, I recommend being wary of buttonMode (and the related useHandCursor) until this gets fixed, especially if you have a SWFs that are loaded and unloaded inside other SWFs. I say that because your buttons will hold many more objects in memory when the SWF can’t unload properly. If you’ve encountered the same bug, please vote for it in my report, “buttonMode = true + mouse click keeps Sprite in memory” filed in the public Adobe bug system.

Be a good SWF citizen, listen for Event.UNLOAD

by Josh Tynjala

If you have a SWF that you know will be loaded into a parent SWF, then it’s your responsibility as a developer to properly clean up after yourself when your child SWF is unloaded. Certain activities will keep your SWF in memory long after the unload() method is called on the Loader object that holds your child SWF, and there’s a good chance that you’ll have created a memory leak if you don’t take care of things properly.

It’s important to note that in Flash Player 10, Loader has a new unloadAndStop() method that does some of this work for you (see Grant Skinner’s blog post about unloadAndStop() for detailed information). While that’s helpful for SWFs that you don’t control, I think it’s still very important to clean up your own SWFs as best you can manually. Obviously, you have no other choice if you’re still targeting Flash Player 9.

What sorts of things could cause problems when you’re trying to unload a SWF? Running Timer instances, enterFrame events, audio or video that’s still playing or streaming, MovieClip instances that are playing, and content loaders that have not completed yet are all good examples (see Grant’s post above for a longer list). Some of these activities don’t actually keep your SWF in memory, but it’s important to remember that your SWF may not be garbage collected immediately. Code that’s still running for no good reason is simply a waste of resources.

How to clean up after yourself

A SWF can discover when it has been unloaded by listening for Event.UNLOAD on its loaderInfo object. This event handler is the perfect place to do your cleanup. I’ve put together a couple of very simple classes to illustrate how this process works.

First, we have the aptly-named ParentSWF document class for the SWF that loads our second SWF as a child. It’s very simple. It instantiates a Loader to load ChildSWF.swf. When that Loader has completed, it has some code to unload ChildSWF.swf right away:

package
{
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;

	/**
	 * This is the document class for a parent SWF that loads, then unloads, a
	 * child SWF. It is part of a demonstration of how child SWFs can clean
	 * up after themselves up when they are unloaded.
	 *
	 * @author Josh Tynjala (joshblog.net)
	 */
	public class ParentSWF extends Sprite
	{
		public function ParentSWF()
		{
			super();

			//this loader will load the child SWF. Upon completion, it will
			//immediately unload the child SWF.
			this._loader = new Loader();
			this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderCompleteHandler);
			this._loader.load(new URLRequest("ChildSWF.swf"));
			this.addChild(this._loader);
		}

		private var _loader:Loader;

		private function loaderCompleteHandler(event:Event):void
		{
			this._loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loaderCompleteHandler);
			this.removeChild(this._loader);

			//this is where we unload the child SWF
			this._loader.unload();
			this._loader = null;

			//at this point, the child SWF should be ready to be garbage
			//collected. only the child SWF can keep itself in memory now.
			//it had better clean up after itself!
		}
	}
}

Next, let’s take a look at ChildSWF, the document class for our second SWF that is loaded into the first SWF. It includes a running Timer that should be stopped when the child SWF is unloaded. We listen for Event.UNLOAD on loaderInfo to know when the parent SWF has unloaded the child SWF:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	/**
	 * This is the document class for a child SWF is loaded in a parent SWF, and
	 * then unloaded. It is part of a demonstration of how child SWFs can clean
	 * up after themselves up when they are unloaded.
	 *
	 * @author Josh Tynjala (joshblog.net)
	 */
	public class ChildSWF extends Sprite
	{
		public function ChildSWF()
		{
			super();

			//we want to listen to Event.UNLOAD so that we know when the Loader
			//in the parent SWF has unloaded this SWF
			this.loaderInfo.addEventListener(Event.UNLOAD, unloadHandler);

			//this timer, if still running, will keep this SWF in memory after
			//ths SWF is unloaded.
			this._timer = new Timer(1000);
			this._timer.start();
		}

		private var _timer:Timer;

		private function unloadHandler(event:Event):void
		{
			//stop the Timer or this SWF won't be garbage collected
			this._timer.stop();

			//of course, make sure to remove this event listener too!
			this.loaderInfo.removeEventListener(Event.UNLOAD, unloadHandler);
		}
	}
}

To be perfectly clear, the most important parts of the child SWF code above can be summarized in the snippet below:

//listen for the unload event to know when all activities in this SWF should be stopped
this.loaderInfo.addEventListener(Event.UNLOAD, unloadHandler);

function unloadHandler(event:Event):void
{
	this.loaderInfo.removeEventListener(Event.UNLOAD, unloadHandler);

	//DO YOUR CLEANUP HERE!
}

Again, let me cover some of the most common things that should be cleaned up. Be sure to stop audio and video that is playing, MovieClip instances that no longer need to be playing, Timer instances, tweens (they’re probably run by Timer instances or enterFrame events), active listeners for any type of Event.ENTER_FRAME, listeners for events coming from the stage (also a good place to use a weak reference), and be sure to stop any data that may be loading from external sources using URLLoader, Socket, Loader, and other classes of that type. If your child SWF has it’s own child SWFs, be sure to unload those too!

That’s a lot of stuff to remember! Yes, it is, and it’s your responsibility as the developer to ensure that nothing in your SWF causes a memory leak, just like it’s your responsibility to do the same for your individual classes. If you’ve designed the rest of your application with good memory management, then it might not be as bad as you think. Often, you only need to take care of objects that are referenced in the document class. Those objects should already be doing their own work to cleanup any references and event listeners that aren’t needed anymore. For more information on that subject, check out the excellent series of blog posts about AS3 memory management by (once again) the very knowledgeable Grant Skinner.

Pages: Prev 1 2 3 4 5 6 7 8 ...46 47 Next