Class Dependencies
One of AS3’s strong suits is its ability to very easily use classes in a dynamic way. Every once in a while, this leads MXMLC to completely remove some of your classes from the output SWF and you then get some very strange behavior. Read on for some strategies for using dynamic classes without going insane.
Just what kinds of dynamic usage are there? Well, one of the most popular is flash.utils.getDefinitionByName
. This function takes a fully-qualified class name (e.g. “com.jacksondunstan.MyClass”, not “MyClass”) and returns you its definition. What is a definition? It’s typed as an Object
, but in actuality it seems to always be a Class
. When the class can’t be found, a ReferenceError
is thrown. If anyone knows of a case where it returns a non-Class
value, please let me know in the comments. Here’s an example using getDefinitionByName
:
function instantiateClass(className:String): Object { try { return new (getDefinitionByName(className) as Class)(); } catch (err:ReferenceError) { return null; } }
The above example is just a simple convenience wrapper for getDefinitionByName
but more complex cases crop up all the time, especially when instantiating based on input data like configuration XML or FlashVars.
The next way you can dynamically instantiate a class is by using an ApplicationDomain
. Your main SWF has the root ApplicationDomain
and you can choose to load other SWFs into so-called “child” application domains. It’s very common to load a SWF of assets—perhaps published from the Flash Authoring Tool—and then instantiate items out of its library/ApplicationDomain
. Here’s an example function that does just that:
function getMovieClipInstance(library:ApplicationDomain, className:String): MovieClip { if (!library.hasDefinition(className)) { return null; } return new (library.getDefinition(className) as Class)(); }
Now that we’ve seen how we can dynamically access classes, let’s talk about how the problem occurs. You see, MXMLC strips out any class it doesn’t think you’re using to save SWF size. While its intentions are noble—who doesn’t like to save file size?—it severely conflicts with the dynamic usage of classes. After all, if we only access classes dynamically, we’ll no longer have any explicit references to them. MXMLC only sees strings in our code and, rightly, doesn’t try to read into how those strings are being used. Still, it will strip out the classes and suddenly we will be getting back a lot of null
values from functions like the above two examples.
So how can we go about making sure that these classes are built into the SWF? I’ll show you two ways to do this, both only requiring minor source code changes.The first is to add some references to the classes in question as fields of any class in your source code:
class MyClass { private static const ClassADependency:Class = ClassA; private static const ClassBDependency:Class = ClassB; private static const ClassCDependency:Class = ClassC; // ... more classes you depend on }
The second approach is to reference the dependent classes in a no-op function with a scary name:
private static function CLASS_DEPENDENCIES_DO_NOT_CALL(): void { ClassA; ClassB; ClassC; // ... more classes you depend on }
Since you have any option, let’s compare them to see which is the better approach:
Fields | Function | |
---|---|---|
Typing | One line (easy copy/paste) + imports | Just the class name + imports |
Execution Speed | Very small initialization at startup | None (if not called) |
Readability | A few extra characters of clutter | Simple list of class names |
Maintainability | Add/remove one line | Add/remove one line |
Accidental Usage | Impossible, even by dynamic nulling | Possible, but cheap and can’t crash |
Personally, I prefer the function-based approach. Which way do you prefer? Do you know of any other good ways of getting around this problem? Feel free to add your own approach in the comments.
#1 by Till Schneidereit on January 24th, 2011 ·
To combine the advantages of both approaches, you can also use a static initializer block:
class MyClass
{
{
ClassA;
ClassB;
ClassC;
// … more classes you depend on
}
}
This is run just once on first usage of the class, or never if you don’t actually ever access the class but only reference it somewhere else.
#2 by jackson on January 24th, 2011 ·
Good one! It might add a bit to SWF size (a class seems heavier than a function or some fields), but it’s nice to have the dependencies in their own file.
#3 by Till Schneidereit on January 24th, 2011 ·
Oh, and getDefinitionByName can also return functions. Example:
flash.utils.getDefinitionByName(‘flash.utils.getDefinitionByName’);
#4 by jackson on January 24th, 2011 ·
I never thought to do that, but you’re right. And if you call it, you’ll realize it’s the actual function:
#5 by Henke37 on January 24th, 2011 ·
getDefinitionByName also returns Namespaces.
#6 by jackson on January 24th, 2011 ·
I tried to get it to return me one:
But I get a
ReferenceError
: “error: ReferenceError: Error #1065”How do you manage to get it to return you a
Namespace
?#7 by skyboy on January 24th, 2011 ·
I think Henke was referring to namespaces defined at the package level: not the namespace of the package itself. The
AS3
namespace for instance.#8 by Miran on January 24th, 2011 ·
You can also include a class by appending its name right after the import statement:
import foo.Bar; Bar;
#9 by jackson on January 24th, 2011 ·
That is an interesting one and I’d never even thought to put a
Class
statement outside of any function or field initialization. It sort-of looks like a typo to me, but perhaps if some formatting could help:#10 by skyboy on January 24th, 2011 ·
Adobe actually uses this methods in one of the Tamarin source files; combining it with the dynamic instantiation function you save the space of a class that’s only purpose is to force inclusion.
#11 by Dan Schultz on January 24th, 2011 ·
Another approach is to use the “-includes” or “-include-libraries” compiler options.
#12 by jackson on January 24th, 2011 ·
That’s a good solution too and one that presents a different approach to maintainability. It presents an interesting question too: is it good to keep the internal dependencies of source code outside of itself? It’s clear that it’s good to keep the external dependencies (e.g. libraries) outside of the source code, such as in a build file, but the question rarely arises with internal dependencies. It’s definitely a question I’ll have to think more about. :)
Thanks for the suggestion!
#13 by George on January 24th, 2011 ·
A method I use for making sure Classes are included into my SWF’s is the registerClassAlias method found in the flash.net package:
#14 by jackson on January 24th, 2011 ·
I think the reason your approach works is because you make an explicit reference to
ClassA
andClassB
in your call toregisterClassAlias
, not thatregisterClassAlias
itself is doing anything. For example, you could replace those calls with calls totrace
:#15 by as3isolib on January 24th, 2011 ·
I have always used the “fields” approach however just recently learned about the the other ways using registerClassAlias and the compiler options.
I think for any professional project, you will almost always have a build script, with you can modify your build.xml (assuming you are using ANT, never used anything but that). The registerClassAlias I have only really seen when you are dealing with data transfer objects using AMF. But now I can see why that would be a related topic.
#16 by Fabien Nicollet on January 26th, 2011 ·
Nice article. For managers such as DragManager, PopUpManager & such, i usually go like this:
private static const DEP:Array = [DragManager, PopUpManager, …]
Fabien
#17 by Jon Williams on February 1st, 2011 ·
I’ve been doing this:
private var forceInclude = [ClassA, ClassB, ClassC, ClassD];
and I’ve wondered whether making it static const like Fabien above is better.
but I do like that Tamarin trick!