I think one of the handiest new features in ActionScript 3 is the Dictionary object, which lives in the flash.utils package. It is a new object type that allows you to associate a value with an object key. This is similar to how array associates values with numeric indexes, and how you can use a generic object to associate a value with a string key.
// Arrays use numeric indexes: var arr:Array = new Array(); arr[0] = "value"; // Generic objects use string indexes: var obj:Object = new Object(); obj["key"] = "value"; // Dictionary uses object keys: var dict:Dictionary = new Dictionary(); dict[myObj] = "value";It's important to understand that Dictionary uses strict equality to match the object, not the reference, as the key. This means that different references to the same object will act as the same key:
import flash.utils.Dictionary; var a:Object = new Object(); var b:Object = a; // reference to the same object var dict:Dictionary = new Dictionary(); dict[a] = "fun!"; trace(dict[b]); // traces 'fun!' because a===bYou can use any type of object as a key, not just generic objects (you could use a Sprite, or an Array, for example). This includes primitives (string, boolean, number, int, uint) which are matched based on value (again, strict equality):
var dict:Dictionary = new Dictionary(); dict["string"] = "joy!"; trace(dict["string"]); // traces "joy" because "string"==="string"Dictionary objects are enumerable with "for in", and "for each" loops:
for (var key:Object in dict) { // iterates through each object key } for each (var value:Object in dict) { // iterates through each value }You can also set a Dictionary to use weak references as keys. This is pretty cool, because it means that if you clear all references to an object except those in a weakly referenced dictionary, that object will be available for garbage collection, which in turn will release the reference to its value.
var a:Sprite = new Sprite(); // create a weakly referenced dictionary by passing true as first param: var dict:Dictionary = new Dictionary(true); dict[a] = new Object(); a = null; // clear original reference to the Sprite.In the above example, the Sprite is now available for collection, which in turn will free the Object for collection. This can be a very handy tool for creating memory management tools, and anywhere that you want to keep lists of objects but not interfere with their collection.
Dictionary objects can be really handy for maintaining object lists / queues, such as a doLater queue for functions (though please note the bug mentioned below), a listeners list for a custom event system, a list of Sprites in a game, or a list of row-renderers in a list component. You could cross-reference an array and a dictionary for quick look-ups in ordered lists (ex. a depth manager) - the array would hold references to objects in the appropriate order, and the dictionary would hold array indexes keyed to the objects themselves.
It can also be used for associating meta data with sealed objects. For instance, a layout manager might need to store extra data about the components it is managing, so instead of injecting that data into the component arbitrarily (which won't work in AS3 anyway), it can maintain a Dictionary that uses weak references to the components as keys, and stores the extra data as the values. This opens the door to a lot of interesting new options from a code architecture perspective.
Note that there is a known bug with Dictionary that prevents it from operating correctly with references to methods. It seems that Dictionary does not resolve the method reference properly, and uses the closure object (ie. the "behind the scenes" object that facilitates method closure by maintaining a reference back to the method and its scope) instead of the function as the key. This causes two problems: the reference is immediately available for collection in a weak Dictionary (because while the method is still referenced, the closure object is not), and it can create duplicate entries if you add the same method twice. This can cause some big problems for things like doLater queues.
I'll be discussing more about how you can use Dictionary objects with weak references in my next article on resource management.
Comments (12)
this sounds very interesting, but I have 1 question: if I have used an object as the key, then later on the object gets garbage collected, if I "for each" through the dictionary has the weak reference just vanished or will there be some residual trace of it (like a null key or something)?
Posted by: chris at July 11, 2006 03:17 AMURL:
:( very confusing. Could you please explain in layman's terms...like how something was done in AS2 and how Dictionary class can help in replacing that to make life simpler.
Posted by: Supriya at July 20, 2006 01:16 AMURL:
"like how something was done in AS2 and how Dictionary class can help in replacing that to make life simpler."
Say you need to check a couple of objects for something (nodes in a pathfinding routine for intsance), and you want to keep track of which objects have been checked this frame. You could set a checked-parameter to true on all of them, which means you'll have to set it to false for the next time you go through the objects.
Or you could store them in an array - ie checkedObjects.push(someObject) but to see if an object has been checked, you'd need to loop through everything to see if the current one has been checked - unless you give them all a unique index or something.
Enter the dictionary. Just do checkedObjects[someObject]=true and to see if it has been checked you can do if(checkedObjects[someObject]) { etc }. Just delete the reference to the checkedObjects dictionary after you're done.
That's just one example that comes to mind. I'm using it for my tile engine too, and there's tons of possible applications for it.
A few days ago, I did some benchmarking, and came to the conclusion that a dictionary is in fact faster than an array, but it was a quick-and-dirty test so I wouldn't call it conclusive at all. I'd like it if Mr Skinner, or anyone else could verify this.
Posted by: GameSQUID at July 20, 2006 08:29 AMURL: http://www.gamesquid.com
Ow, and another thing that's cool about dictionaries is that you can easily delete an object from it.
Posted by: GameSQUID at July 20, 2006 08:34 AMIf you'd use an array, you would have to loop through it to find the index of the object, and splice the array there. In a dictionary, since the objects are the keys, you can just delete myDictionary[someObject], which is easier, and probably faster (haven't checked, but it should be, especially when there's lots of data in it).
URL: http://www.gamesquid.com
This is very cool I use a function like mentioned above that I dance through arrays regurlarly to look up an object base on instance name of the movie clip. This would be great for going directly to the OBject instance based on the object itself. And not have to loop through an array and match it based on name. I do like doing look up's using Named Value pairs. But this seems much more poweful looking for an object match instead. VERY NICE! Thanks Grant!
Posted by: Metallikiller at July 31, 2006 10:59 PMURL: http://www.everything4me.com/comsvr/
Hi Grant,
I'm enjoying your blog, thanks!
For the "key matching by value" issue, however, I've come to different results. In my experience, the comparison is done how it's always done in AS: primitve data types are compared by value while composite types are compared by reference. If we use, for example, Strings as key (even if we type them as Objects), the comparison is done by value:
var s1:Object = "string";
var s2:Object = "string";
trace("s1==s2:"+(s1==s2));
This is not the case for objects. See this example:
import flash.utils.Dictionary;
var a:Object = { x:3 };
var b:Object = { x:3 };
var c:Object = a;
var s1:Object = "string";
var s2:Object = "string";
trace("a==b:"+(a==b) + " a===b:"+(a===b));
trace("a==c:"+(a==c) + " a===c:"+(a===c));
trace("s1==s2:"+(s1==s2) + " s1===s2:"+(s1===s2));
var dict:Dictionary = new Dictionary();
dict[a] = "aaaa";
dict[b] = "bbbb"; // does not overwrite dict[a] because b's a different object (comparison by reference)
dict[c] = "cccc"; // overwrites dict[a]
dict[s1] = "s1";
dict[s2] = "s2"; // overwrites dict[s1] because of string comparison (by value)
for (var key:Object in dict) {
trace("dict["+key+"] = "+dict[key]);
}
Strict comparison does not influence whether the comparison is done by value or by reference but rather says the operand must be equal in value *and* type.
Posted by: david at August 9, 2006 03:24 AMURL: http://www.illustree.com
i don't believe the weak reference option for the dictionary class works. i used the following test (because i can't your duplicate localconnection to work):
first, in the authoring environment i create a movieclip with a few frames and attach a trace(this) on the last frame and assign it a class, testClip. it's not placed on-stage.
next, attached to the first frame of the main timeline i use:
//******** begin code ************
var mc:MovieClip=new MovieClip();
var dict:Dictionary=new Dictionary(true);
dict[mc]=new testClip();
mc=null;
//delete dict[mc];
// the code below is to force garbage collection
var t:Timer=new Timer(100,0);
t.addEventListener(TimerEvent.TIMER,f,false,0,true);
t.start();
function f(evt:TimerEvent) {
for (var i:int=1; i this["mc_"+i+" "+evt.target.currentCount]=new MovieClip();
}
trace(System.totalMemory/1024,"kb");
}
// ************ end code **************
i find the trace output continues revealing testClip still exists. if i delete the key, then testClip is garbage collected sometime after the player consumes 7500kb of memory which is the typical time for first sweep in my other (unrelated) tests.
Posted by: kglad at June 24, 2007 09:34 AMURL:
that gc forcing code should be:
var t:Timer=new Timer(100,0);
t.addEventListener(TimerEvent.TIMER,f,false,0,true);
t.start();
function f(evt:TimerEvent) {
Posted by: kglad at August 6, 2007 09:14 PMfor (var i:int=1; i this["mc_"+i+"_"+evt.target.currentCount]=new MovieClip();
}
trace(System.totalMemory/1024,"kb");
}
URL:
there must be some formatting issue with this forum that causes that code to be mangled.
Posted by: kglad at August 10, 2007 09:04 PMURL: http://www.kglad.com
kglad, I have written a Queue class [doLater] utilizing a Dictionary Object for storage of functions and their parameters. The weak referencing does indeed work. You can find the source for my Queue class here -> http://blog.efnx.com/?p=37 . Using that class in another fla it's easy to see the functions instantly being gc'd with this code:
import com.efnx.utils.Queue;
function blah(val1:int, val2:int):void
{
trace(val1+val2);
}
function blah2(val1:int, val2:int):void
{
trace(val1+val2);
}
var queue:Queue = new Queue(true); // queue.push(blah, 1, 5);
queue.push(blah, 2, 5);
queue.push(blah, 4, 5);
queue.push(blah2, 3, 5);
trace(queue);
var timer:Timer = new Timer(1, 1);
timer.addEventListener(TimerEvent.TIMER, startQueue, false, 0, true);
timer.start();
function startQueue(event:TimerEvent):void
{
queue.start();
trace(queue);
trace("done");
}
I hope this clears it up...
Posted by: Schell at December 8, 2007 01:39 AMURL: http://blog.efnx.com
How to find out the number of elements in Dictionary without iterating through each object/key?
Posted by: Ilya at February 7, 2008 12:51 AMURL:
There is no length property for a Dictionary so I use a separate counter such as numElements:uint
It's a few extra lines but iteration is much more expensive.
Posted by: Anna at May 9, 2008 09:30 AMURL: