The complete solution for implementing a deep-copy clone() method in ActionScript 3

When I started out to write a clone method for a few of my AS3 classes, I looked online and didn’t find any official information about cloning classes from Adobe. There were however a few solutions that were hacked together to provide the same functionality (here and here). These solutions have one big problem though, they don’t work with classes which have non-primitive properties.

My solution mostly builds on top of these solutions but it also provides the ability to clone classes which have properties that are custom class. And it also works with inheritance.

I’ve placed all helper methods in a CloneUtility class for better modularity. We’ll be using these methods in the code examples below.

public class CloneUtility
 {
 /**
 * This method registers an alias for a class. This registration is required for writing and reading the
 object in AMF format. If the object is not registered, a runtime error will be thrown when the object is written
 in preparation for a clone.
 *
 * @param object The object to register.
 * @return void
 */
 public static function registerClass(object:Object) : void {
 var qualifiedClassName : String = getQualifiedClassName(object).replace( "::", "." );
 registerClassAlias(qualifiedClassName, getDefinitionByName(qualifiedClassName) as Class );
 }
/**
 * This method writes the passed object to a byte array. This method is supposed to be used in the
 clone() method of the object that is passed.
 *
 * @param object The object to clone.
 * @return void
 */
 public static function writeObjectToByteArray(object:Object) : ByteArray {
 var qualifiedClassName : String = getQualifiedClassName(object).replace( "::", "." );
 var bytes : ByteArray = new ByteArray();
registerClassAlias(qualifiedClassName, getDefinitionByName(qualifiedClassName) as Class );
 bytes.writeObject(object);
 bytes.position = 0;
 return bytes;
 }
 }

For an example of how to make your classes cloneable, I’ll go with the example of class A and its subclass B. (B extends A)

This technique of making a deep copy works by writing the object to a bytestream, and then reading it back in as a new object. To facilitate this process, the super class (A) will need to implement the IExternalizable interface, which requires the implementation of two methods:

public function writeExternal(output:IDataOutput):void
public function readExternal(input:IDataInput):void

The parent class would look something like this (explained with inline comments).

public class A implements IExternalizable
{
private var prop1:String;
private var prop2:int;
public function A()
{
// we need to register class to be able to write and read it back successfully
CloneUtility.registerClass(this);
}
/**
* This method is responsible for returning a deep copy of this object.
*
* @return the cloned copy of this object
*/
public function clone():A {
var bytes : ByteArray = CloneUtility.writeObjectToByteArray(this);
return bytes.readObject() as A;
}
/**
* All properties of the class must be written to output.
*
* @param output The stream to which to write to
* @return void
*/
public function writeExternal(output:IDataOutput):void {
output.writeUTF(prop1);
output.writeInt(prop2);
}
/**
* All properties of the class must be read from the input, in the same order as they were written out.
*
* @param input The stream from which to read from
* @return void
*/
public function readExternal(input:IDataInput):void {
prop1 = input.readUTF();
prop2 = input.readInt();
}
}

Note that the writeExternal() and readExternal() methods will be invoked when the clone method is invoked (by the write and read calls).

The sub-class, B, would look something like this.

public class B extends A
{
private var prop3:uint;
/**
* The constructor registers this class to enable it for cloning.
*/
public function B()
{
super();
// subclasses need to register themselves as well
CloneUtility.registerClass(this);
}
/**
* This method writes all properties of this object to output.
*
* @param output The stream to which to write to
* @return void
*/
public override function writeExternal(output:IDataOutput):void {
super.writeExternal(output);
output.writeUnsignedInt(prop3);
}
/**
* This method reads all properties of this object from input.
*
* @param input The stream from which to read from
* @return void
*/
public override function readExternal(input:IDataInput):void {
super.readExternal(input);
prop3 = input.readUnsignedInt();
}
}

Note that the sub-class doesn’t need to override the clone method, just the writeExternal() and readExternal() methods.

So far it’s been pretty simple. The cool thing however is that you can also easily clone classes that have a custom class (which supports cloning) as a property. Below is code of a class C that has a property of type B.

public class C implements IExternalizable
{
private var customProp:B;
/**
* The constructor registers object to enable cloning.
*/
public function C():void
{
CloneUtility.registerClass(this);
}
/**
* Make a deep copy of this object.
*
* @return A duplicated object of type C
*/
public function clone():C
{
var bytes : ByteArray = CloneUtility.writeObjectToByteArray(this);
return bytes.readObject() as C;
}
/**
* This method writes all properties of this object to output.
*
* @param output The stream to which to write to
* @return void
*/
public function writeExternal(output:IDataOutput):void {
output.writeObject(customProp);
}
/**
* This method reads all properties of this object from input.
*
* @param input The stream from which to read from
* @return void
*/
public function readExternal(input:IDataInput):void {
// note that you should cast as the parent custom class (if there is one)
customProp = input.readObject() as A;
}
}

Note that you will use output.writeObject() and input.readObject() for custom classes. These methods will invoke the writeExternal() and readExternal() methods that are implemented by those custom classes.

Let me know if this works for you or if you have any suggestions for improvements!

Tagged , ,

2 thoughts on “The complete solution for implementing a deep-copy clone() method in ActionScript 3

  1. Olivier says:

    Hi,

    thank you for that it’s very helpful.

    But how can we do when subclass property isn’t custom property ?

    In my case, I have a HitData type in a subclass property and I have an error message :

    TypeError: Error #1034: Echec de la contrainte de type : conversion de Object@4b5a461 en mx.charts.HitData impossible. (French version sorry)

    I can’t modify HitData class and extends this class doesn’t seem to be a good idea I think

    Ty

    cheers

    • Hmm, yeah that’s a problem because you need to be able to modify the HitData class to be able to use the CloneUtility. Sorry, I haven’t come across a case like yours to be able to help you with a solution.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: