« A Factory for Tweening 'No-brain' getter and setters »

February 18, 2005

Know thy Class Library

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!

Comments

  • I have Flash 7.0.2 and the only thing in my mx.utils is Delegate. No ObjectCopy. I think I'm also missing Collection and Iterator...

  • ...because they come with Flex, not Flash

  • No, they ship with Flash MX 2004 Professional. Sorry, should've been more clear on that.

  • actually, iirc it comes with the remoting sourcecode in 2004

    http://www.macromedia.com/software/flashremoting/downloads/components/

    file is "flashremoting_comp_sourcecode.zip"

  • Thanks collin, you're right about that. I've updated the entry itself to reflect that update.

    Like I said in the entry, I've had the Flash Remoting components installed for so long that I just assumed ObjectCopy came bundled with Flash. :-)

  • Hey, I did'nt know about mx.utils.ObjectCopy.. Excellent, thx.

  • Does ObjectCopy resolve circular references?

  • No it doesn't resolve circular references. Thus, you'll get the 256 levels of recursion error if you try to copy something with a circular reference.

    You would have to write your own, or find a different copying class, to handle those type of situations.

  • A question on mx.utils.ObjectCopy usage:

    Why can't you copy a copy?

    Without using classes and just doing:

    var obj2 = ObjectCopy.copy(obj1);

    Assuming obj1 is some object with properties/values) all is well and you can view / modify all of obj2 values.

    if you then do:

    var obj3 = ObjectCopy.copy(obj2);

    obj3 is undefined and has not copied anything from obj2.

    Can someone explain to me why this is the case?

    Thanks.

  • import mx.utils.ObjectCopy;

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

    // our first attempt at coping object
    var obj2:Object = new Object(ObjectCopy.copy(obj1))
    obj2.color= "blue"

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

  • Hi guys,

    thanks for this post, I built my own copyobject class some time ago, but this one seems to be pretty more powerfull than mine. However, I haven't been able to make it work. I've downloaded the remoting source code but i keep on getting "undefined" and "false" on the trace lines of your example. Does anybody know what the problem can be?
    Also, is there any document with all these libraries documented? Thanks a lot.

    Daniel

  • Hmm.. I think I remember a long time ago running into the same issue, where the copy would return undefined. The answer was a small mistake in the source code.

    Open up ObjectCopy.as in the mx\util directory of you Flash Installation Classes directory (something like C:\Program Files\Macromedia\Flash MX 2004\en\First Run\Classes), and change this line:

    var result:Object = new Function( refObj.__proto__.constructor)();


    .. to be this:

    var result:Object = new (Function( refObj.__proto__.constructor))();

    All I did was add a set of parenthesis. There was a problem with the function cast not grouping correctly. Try that, and everything should work ok.

  • Hi,

    Thanks Collin for pointing that the source files contains TONS of other utils classes !!
    Even DataGlue wasn't included in my conf files !!
    Thanks a bunch…

  • Darron.

    Thanks! Your one line fix did the trick. I can now copy copies :)

    I can now use this class again (I got frustrated and wrote my own (albiet not as powerful))

    Is MM aware of the problem?

  • Great article!! Thanks, this is good stuff. I'm sure that the info will be useful.

  • I was really excited about this until I tried it in my code on an object with another object saved on it as a property and it didn't work.

    On the plus side, your copyObject code works great. :)

  • some one haveing this problem in Flex 1,5 ?

Post a comment

Remember personal info?