December 31, 2006

Static Initializers in AS3

I couldn't find much official documentation (if you have links please pass them along) about static initializers in ActionScript 3 so I thought I'd put together a little quick reference. A static initializer is run once when a class is first loaded by the AVM. The typical user of such a block is the compiler, which uses it to assign values to static variables of the class. You also can use it to do more complex static object initialization. The example that I needed it for was to create a lookup object based on constants defined in the class. Consider a class that defines some handy constants:

package com.example {
    public class MIME {
        public static const GIF_MIME_TYPE:String = "image/gif";
        public static const JPG_MIME_TYPE:String = "image/jpeg";
        public static const PNG_MIME_TYPE:String = "image/png";
        public static const SWF_MIME_TYPE:String = "application/x-shockwave-flash";
    }
}

If you wanted to create a lookup table to quickly check if a random string matched one of these you can't use the standard object literal initialization syntax. Consider if this code is added to the MIME class:

// Don't use this, it doesn't do what you think, see below
private static const IMAGE_TYPES:Object = {GIF_MIME_TYPE:true,
    JPG_MIME_TYPE:true,
    PNG_MIME_TYPE:true,
    SWF_MIME_TYPE:true};

public static function isImage(mimeType:String):Boolean {
    return (IMAGE_TYPES[mimeType] == true);
}

If you run this test code, you don't get what you want:

trace(MIME.isImage(MIME.GIF_MIME_TYPE));
// Output: false

False? What's going on is that the text before the : in the object literal is treated as a literal string, not as a variable. What ended up happening was the literal string "GIF_MIME_TYPE" was stored in the associative array. This can be demonstrated by this little test:

trace(MIME.isImage("GIF_MIME_TYPE"));
// Output: true

Obviously not what I wanted. This is where the static initializer comes in. I only want to have to setup the lookup table once and avoid having to track if it is setup, so I put the initialization code in a block at the class level.

package com.example {
    public class MIME {
        private static const IMAGE_TYPES:Object = new Object();
        // This block is run once when the class is first accessed
        {
            IMAGE_TYPES[GIF_MIME_TYPE] = true;
            IMAGE_TYPES[JPG_MIME_TYPE] = true;
            IMAGE_TYPES[PNG_MIME_TYPE] = true;
            IMAGE_TYPES[SWF_MIME_TYPE] = true;
        }
    }
}

Yes those lines are just hanging out in the class itself and not within some method. Truth be told the {} are optional. I like to put them in a block to offset the code and make it more readable. With this updated version of IMAGE_TYPES our two tests from above now do the right thing:

trace(MIME.isImage(MIME.GIF_MIME_TYPE));
// Output: true
trace(MIME.isImage("GIF_MIME_TYPE"));
// Output: false

I should point out that the use of const in the static variable declaration is also a preference thing. Even though the IMAGE_TYPES lookup table is defined as a static constant, the contents of the object can still change at runtime. The only thing that's can't change is the object reference that IMAGE_TYPES points at. For example the following code is invalid:

package com.example {
    public class BAD {
        private static const IMAGE_TYPES:Object = new Object();
        {
            // This is a compile time error
            IMAGE_TYPES = new Object();
        }
    }
}

Trying to compile that will give you the error:

1049: Illegal assignment to a variable specified as constant.

The completed MIME class now looks like this:

package com.example {
    public class MIME {
        public static const GIF_MIME_TYPE:String = "image/gif";
        public static const JPG_MIME_TYPE:String = "image/jpeg";
        public static const PNG_MIME_TYPE:String = "image/png";
        public static const SWF_MIME_TYPE:String = "application/x-shockwave-flash";

        private static const IMAGE_TYPES:Object = new Object()
        // This block is run once when the class is first accessed
        {
            IMAGE_TYPES[GIF_MIME_TYPE] = true;
            IMAGE_TYPES[JPG_MIME_TYPE] = true;
            IMAGE_TYPES[PNG_MIME_TYPE] = true;
            IMAGE_TYPES[SWF_MIME_TYPE] = true;
        }

        public static function isImage(mimeType:String):Boolean {
            return (IMAGE_TYPES[mimeType] == true);
        }
    }
}

I get a handy lookup table populated by constants instead of strings, my test function is short and simple instead of some long if then block, and I don't need to worry about checking each time if my lookup table has been initialized. I'm happy.

Tags: as3 flex initializer static