February 2005 Archives

Know thy Class Library

| 17 Comments | 1 TrackBack

ObjectCopy gets no love.

There is more to programming than just syntax (knowing how a language is structured). Being an effective programmer often requires knowing what's available to use - code that are already written for you that you can leverage without having to spend time writing it yourself. In ActionScript 2, this code comes in the form of class libraries. Class libraries often provide functionality that enable you to be more effective and complete your job in a more time efficient manner, as well as provide tools to make solving problems easier.

That being said, I see this question all of the time: "How do I copy an object?." The question stems from the fact that complex types in Flash are reference types. In contrast, primitive types are value types. The primitive types are number, string, boolean, undefined, and null. Everything else is a reference type. When you have a variable that is a reference type, the variable itself doesn't contain the information about the object, rather it just contains a reference to the location of that object in memory. For primitive types, variables actually contain the data as opposed to containing a reference to the data. There are some Computer Science explanations for this that I won't get into here, but I'm sure you can find them on google.

When we attempt to copy an object via normal assignment, what actually happens is that the new variable is set to the reference of the object we're attempting to copy. Instead of making a new object, like what we probably wanted to do, we've just created 2 ways to refer to the same object. A simple example:

var obj:Object = new Object();
obj.color = "red";
obj.count = 5;

// our first attempt at coping object
var obj2:Object = obj;
obj2.color= "blue"

trace(obj.color);  // blue
trace(obj2.color); // blue

When we modify the name in obj2, the name in obj also gets modified. That is because the = assigns a reference to obj2, rather than creating a copy of the object. That is, obj2 and obj refer to the same object in memory.

It doesn't take long for someone to answer the question. Usually it involves some sort of loop that loops over all of the properties inside of the object. Because of the value / reference problem with copying, what you need to do is break the object down into it's primitive types, and then copy those. The primitive types are value types, so when we copy them we get a brand new value in memory that has the same data but is independent of the original value, which is precisely what we want. This is often called a "true copy". To copy the complex reference types, we'll simply create a "new" object, and then store all of the same primitives in it. Creating a new obejct gives us a new reference, so that eliminates the dual reference problem. That code typically looks like this:

function copyObject(obj) {
    // create a "new" object or array depending on the type of obj
    var copy = (obj instanceof Array) ? [] : {};
    
    // loop over all of the value in the object or the array to copy them
    for(var i in obj) {
       // assign a temporarity value for the data inside the object
       var item = obj[i];
        // check to see if the data is complex or primitive
       switch(item instanceof Array || item instanceof Object) {
          case true:
             // if the data inside of the complex type is still complex, we need to
            // break that down further, so call copyObject again on that complex
            // item
            copy[i] = copyObject(item);
             break;
         default:
            // the data inside is primitive, so just copy it (this is a value copy)
            copy[i] = item;
      }
   }
  return copy;
} 

Sample usage:

var obj:Object = new Object();
obj.color = "red";
obj.count = 5;

// our second attempt at coping object
var obj2:Object = copyObject(obj);
obj2.color= "blue"

trace(obj.color);  // red
trace(obj2.color); // blue

As you can see, this method worked great. We now can copy an object without having to worry about any of the "reference stuff." However, there's a problem with this code. Do you know what it is? It's not obvious at first, but if you try to copy an instance of a custom class, the code doesn't work quite right. Why? Because the copy of the object will not be an instance of the class, since when we copy the object we never call the class constructor! An example of that:

// Dog.as
class Dog {
	public var name:String;
	
	public function Dog(name:String) {
		this.name = name;
	}
	
	public function speak():String {
		return name + " says, 'Arf arf!'";
	}
}
// in a .fla file:
var dog1:Dog = new Dog("Yelowdog");

// clone Yellowdog because my puppy is just that cute
var dog2:Dog = copyObject(dog1);
dog2.name = "Yellowdog Clone";

trace( dog1.speak() );	// Yelowdog says, 'Arf arf!'
trace( dog2.name ); 	// Yellowdog Clone
trace( dog2.speak() );	// undefined
trace( dog2 instanceof Dog ); // false

You can see that the copy "kind of" worked. That is, modifying the name of dog2 didn't change the name of dog1. However, in the copy process we lost all of the information related to our class, and you can see that dog2 is not an instance of Dog after the copy.

How do we fix this? The answer is simple - we don't. Instead, lets use the tools that are available to us. This problem has long been a problem, and as such there are already solutions available, right under our nose too. All we need to do is look at one of the classes that ship with Flash MX 2004. It's mx.utils.ObjectCopy, and it's very simple to use and hides all of that "reference stuff" from us so we don't have to deal with it.

import mx.utils.ObjectCopy;

var dog1:Dog = new Dog("Yelowdog");

// use ObjectCopy to do the copy, and because we know the result
// is going to be a Dog, use the Dog cast so we don't get compiler errors
var dog2:Dog = Dog( ObjectCopy.copy(dog1) );
dog2.name = "Yellowdog Clone";

trace( dog1.speak() );	// Yelowdog says, 'Arf arf!'
trace( dog2.name ); 	// Yellowdog Clone
trace( dog2.speak() );	// Yellowdog Clone says, 'Arf arf!'
trace( dog2 instanceof Dog ); // true

Moral of the story: While it's good to reinvent the wheel if you're interested in the learning process, if you're just interested in the results of what the wheel does, you can leverage class libraries to re-use code already written. Flash MX 2004 ships with a decent amount of classes which you can find on Windows in somewhere like C:\Program Files\Macromedia\Flash MX 2004\en\First Run\Classes. There are also class libraries available on-line. Here's a short list, in no particular order

I'm sure there are other lass libraries as well (feel free to add more in the comments). Note that because ActionScript and JavaScript are very similar, chances are you can re-use code from JavaScript libraries without much modification. In fact, a lot of JavaScript code requires nothing more than a copy/paste into Flash for it to work.

As always, enjoy exploring...

UPDATE: It seems as though you need to download the Flash Remoting Soure Code to use the ObjectCopy class. I've been doing this for so long that I thought it shipped with Flash. Sorry about that!

A Factory for Tweening

| 7 Comments | No TrackBacks

You've probably seen this example before - select a tween type from a list and click a button to watch an object move accordingly. I've seen the "how" question for this come up quite a bit and thought I'd share my solution, using the Factory design pattern.

The Factory design pattern is a creational pattern. You describe to the factory what you would like and it goes about it's business in creating it for you. This is particular useful if you don't really know what you want. In this example, you know you're going to need a tweening function, but you don't really know what tweening function you need until the program is actually running. You can defer the selection by using the Factory pattern. Enter EaseFactory.

I've created an EaseFactory class to wrap the the mx.transitions.easing package making dynamic tween selection a bit easier. Simply describe the type of tweening function you would like, and it will return the appropriate reference for you.

Here is the example in action.



About this Archive

This page is an archive of entries from February 2005 listed from newest to oldest.

January 2005 is the previous archive.

March 2005 is the next archive.

Find recent content on the main index or look in the archives to find all content.

Archives

OpenID accepted here Learn more about OpenID
Powered by Movable Type 5.02