In Actionscript 3, you write destroy() methods to clear up anything you’re not using:
public function destroy():void
{
removeChild(things-I-put-onto-the-stage);
things-I-put-onto-the-stage = null;
variables-I-created-in-this-class = null;
}
However, in Objective-C, you deallocate memory for anything you created when the class was instantiated:
- (void)dealloc {
[variables-I-created-in-this-class release];
[super dealloc];
}
Today I started reading Practical Memory Management for developing applications for the iPad and iPhone. It seems like the majority of issues in developing for these devices revolves around memory, so I thought, why not start at the beginning?
Unlike Flash, the iPhone and iPad have no garbage collection. This means that anything you create has to be destroyed explicitly by you.
In a way, I am not sure if this is any worse than Flash – Flash has garbage collection, but you have to mark items for garbage collection by calling removeChild and / or delete and / or myVariable=null. In other words, Flash has garbage collection, but then Flash misbehaves unless you “take the garbage out to the street” – so what’s the real difference?
I think what might be the difference is with Flash you wait for the garbage collector to get rid of objects that are taking up memory, whereas with Objective-C (which is the language used for iPhones and iPads) you actively dispose of objects and the memory its taking up on your own. This means that you don’t have to wait around for the garbage collector to do it. That’s a real advantage.
Actionscript 3:
private function set count (newCount:Int):void
{
_oldCount = newCount;
}
Objective-C:
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[oldCount release];
// make the new assignment
oldCount = newCount;
}
So – other than the obvious language differences, what’s the big difference?
[newCount retain]; [oldCount release];
That’s right – you have to garbage the old and explictly retain the new. Every time!
In this post I’d like to talk about other things you can do to make your projects run better, be less buggy, and use less memory.
The format of your class, if it’s a MovieClip, a Sprite, a .swf file you are loading into another swf, or any type of display object that’s interactive, should look like below:
package {
import flash.display.MovieClip;
import flash.events.Event;
public class MyLoadedMovieClip extends MovieClip {
// class variables here
public function MyLoadedMovieClip()
{
// variables created in the constructor
addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true);
}
private function init(e:Event):void
{
//
}
public function dispose():void
{
// stop Timers
// remove event listeners
// remove *some* class variables
}
}
The dispose function is used to help mark items in the class for garbage collection. Things that should be marked for garbage collection include anything that’s going to keep the flash player working hard for no reason. This includes:
If you don’t need any of these things, you should get rid of it. That’s what the dispose function does.
Example usage:
// create an instance
var item:MyLoadedMovieClip = new MyLoadedMovieClip();
// add it to the stage
addChild(item);
// to dispose, remove it from the stage
removeChild(item);
// call the dispose function
item.dispose();
// null the reference
item = null
So…aside from stopping your timers and removing event listeners, which is straightforward, here are some finer points about what to remove in your dispose() function.
A class variable is any variable declared before you constructor function.
For example:
package {
import flash.display.MovieClip;
import flash.events.Event;
public class MyLoadedMovieClip extends MovieClip {
public var my_graphic_that_is_on_stage:Star = new Star;
private var my_graphic:PacMan;
private var my_other_graphic:PacMan;
private var A:String;
private var B:Number;
private var C:Vector.;
private var something_you_need_before_object_is_on_stage:String;
public function MyLoadedMovieClip()
{
All that stuff in bold are class variables. Most of these you should clear out in your “dispose” or “destroy” function (more on that later). I say “most” because there are a few of these variables that’s actually been created before this class has even hit the stage, and these ones should not be in your dispose() function.
Instances become public variables in your class. Because this variable was already on the stage of object (not the root stage or the “stage” stage) before the the constructor function, you shouldn’t put it in your dispose() or destroy function.
Likewise, any class variable that you instantiate (I mean by using the new keyword) in your constructor function should also not be in your dispose or destroy method.
public function MyLoadedMovieClip() { something_you_need_before_object_is_on_stage = "really important"; addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true); }
So, that class variable,
something_you_need_before_object_is_on_stage
should really not be in your dispose function.
This is because when it comes to stuff like Sprites and MovieClips, these kind of objects are often reused – added to the stage and removed from the stage.
If you remove that object from the stage, and then call it’s dispose function, those needed variables will be gone and it will cause you a major headache.
Anything that you’ve put in your init() function (when your object arrives on the “stage” stage)…dispose!
As another note…the init() (short for “initalize”) function is a good best practice function. Your constructor should be as small as you can make it (or better yet, empty).
I like to make my init() function get called when my object is added to the stage:
addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true);
Variables that are display objects (such as Sprites or MovieClips) should be removed from the stage and then made null.
removeChild(my_graphic);
removeChild(my_other_graphic);
my_graphic = null;
my_other_graphic = null;
Strings can be made null, and Numbers can be undefined (there’s probably a better way to clear numbers…if so let me know)
myString = null;
myNumber = undefined
Vectors or arrays can have their length trimmed to 0
MyVector.length = 0
Actually, this post isn’t really about using loadMovie with Actionscript 3, but about using the Actionscript 3.0 version of loadMovie, and using it with SWFAddress.
First your class variables:
private var isLoaded:String;
private var currentLoader:Loader;
private var isHome:Boolean = true; // home page is the first page
I use a variable called isLoaded to tell me what .swf or “section” of my site I’m am going to load. When there is something in “isLoaded” not only do I know what section (swf) I am loading, I also know that something has loaded and so this prevents me from accidentally loading the .swf twice.
Secondly – and this is even more important – I use one loader to load all of my movies (swfs). I use the same loader to load, and when I unload, I use unloadAndStop instead of unload().
If you use a different loader for each .swf, you will have problems because even though you uloaded the swf file, a reference for that file exists (i.e. the loader). If that reference exists, the .swf won’t be marked for garbage collection and your project will act buggy.
By using the same loader over and over, it’s a) easy to keep track of, and b) easy to clear out and reuse for other .swf files.
Next, my init() function:
private function init():void
{
SWFAddress.addEventListener(SWFAddressEvent.CHANGE,
changeAddress, false, 0, true);
}
When my Main swf (MovieClip) is on the stage, I listen for the SWFAddress event:
private function changeAddress(e:SWFAddressEvent):void
{
if (e.value != "/")
{
SWFAddress.setTitle("Your-home-page-title" + e.value.substring(1));
}
else
{
SWFAddress.setTitle("Your-home-page-title");
}
switch(e.value)
{
case "/what-you-want-your-link-to-be-called":
loadSWF("what-you-want-your-link-to-be-called", "my-swf.swf");
break;
case "/what-you-want-your-2nd-link-to-be-called":
loadSWF("what-you-want-your-2nd-link-to-be-called", "my-swf2.swf");
break;
}
}
e.value is the “address” of each .swf file. You can set this value by calling:
SWFAddress.setValue("what-you-want-your-link-to-be-called");
Whenever you call this method (setValue)…the address in your URL will change to, say:
http://www.my-project.com/index.php#/what-you-want-your-link-to-be-called
So, in my switch statement above, if a new address gets set, it calls a loadSWF function:
private function loadSWF(value:String, url:String):void
{
if (isLoaded == value) return;
isLoaded = value;
unloadCurrent();
currentLoader = new Loader();
currentLoader.load(new URLRequest(url));
currentLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,
startSWF, false, 0, true);
}
So, a few things here.
If there is already an address set and it’s the same address (isLoaded == value) then the function basically stops (by using return). This is just a little safeguard to stop me from loading the same .swf file twice.
if (isLoaded == value) return;
Next, if it’s a different value (i.e. a different .swf or /address) I set the isLoaded to the new value:
isLoaded = value;
After this, I make sure to unload any .swf that’s already been loaded:
unloadCurrent();
Following that is some pretty standard loader code to load the swf, followed by a function that actually adds the content of the loader to the stage once the loader has finished loading:
private function startSWF(e:Event):void
{
addChild(currentLoader.content);
}
Note: I added the loader.content, instead of the loader. While you can add the loader, the behaviour is unexpected – add the content of the loader instead.
Below is some code to load the movieclip and change the address:
myButtonMovieClip.addEventListener(MouseEvent.CLICK, onMouseClick, false, 0, true);
private function onMouseClick(e:MouseEvent):void
{
SWFAddress.setValue("what-you-want-your-link-to-be-called");
}
Below is a way to unloadAndStop. I found just using unloadAndStop() wasn’t enough – it’s better to remove the loader.content from the stage, then unloadAndStop, then null the loader.
private function unloadCurrent():void
{
if (isLoaded && currentLoader != null && this.contains(currentLoader.content))
{
removeChild(currentLoader.content);
currentLoader.unloadAndStop();
currentLoader = null;
isLoaded = null;
}
}
I also made a “Home” button to go back to my “home” or “main” swf
private function goHome(e:CustomEvent):void
{
if (!isHome)
{
SWFAddress.setValue("/");
unloadCurrent();
}
}
If you’ve ever coded a flash project where things start to act really wrong, try checking your timers (even if you have already).
Without getting into a really detailed explanation of how garbage collection works (or why you need to care about it), if you start a timer in actionscript 3.0 and you never stop it, that is a really easy way to crash someone’s browser (or, at the very least, cause buggy behaviour in your flash project).
Example 1:
var myTimer = new Timer(500); // runs every half second myTimer.start();
If that’s all you ever wrote, that timer would fire off every half-second…forever. If you have any functions that fire from that timer:
var myTimer = new Timer(500); // runs every half second
myTimer.addEventListener(TimerEvent.TIMER, runAnimationHere);
myTimer.start();
function runAnimationHere(e:TimerEvent):void
{
trace ("hello!" + e);
}
That sample above runs a function that traces out “hello!” every 5 seconds and never stops. That’s a bit more memory used…a bit more CPU used…and a bit more of a chance that your project is going to start acting up or someone’s browser might crash visiting your site.
Below is a bit of a “better practice”:
var myTimer = new Timer(500); // runs every half second
myTimer.addEventListener(TimerEvent.TIMER, runAnimationHere);
myTimer.start();
function runAnimationHere(e:TimerEvent):void
{
trace ("hello!" + e);
myTimer.removeEventListener(TimerEvent.TIMER, runAnimationHere);
}
By adding a “removeEventListener” when your timer fires off, you’ve done a bit of cleanup. However, there’s still another problem…the timer is still running!
var myTimer = new Timer(500); // runs every half second
myTimer.addEventListener(TimerEvent.TIMER, runAnimationHere);
myTimer.start();
function runAnimationHere(e:TimerEvent):void
{
trace ("hello!" + e);
myTimer.removeEventListener(TimerEvent.TIMER, runAnimationHere);
myTimer.stop();
}
Adding a stop() to the Timer stops the timer from firing.
1. If you start a timer, remember to stop it.
2. If you add an event listener for a timer, remember to remove the event listener somewhere later in your code.
3. Watch out for the scope in which you create your timers. By this, I mean, try not to create a bunch of timers (say, inside the scope of a given function) when you can reuse a single timer by turning it on and off when you need it. The more times you use the new word:
var myTimer = new Timer(500);
The more timers you will have to hunt down to turn off later.