Checking Class Inheritance
I recently received a tip about a thread discussing an interesting problem: how to tell if one Class
object represents a class that subclasses another Class
object. If you had an instance of the class, you could simply use the is
or instanceof
keywords, but that won’t do here. Today’s article shows how to solve this tricky problem.
The aforementioned thread points to a couple of workable solutions. The first uses a combination of getDefinitionByName
and getQualifiedSuperclassName
in a loop to iterate until the superclass is found. Here is the version posted by player_03
: (formatting applied by me)
private function isSubclassOfP03(type:Class, superClass:Class): Boolean { if (superClass == Object) { return true; } try { for ( var c:Class = type; c != Object; c = Class(getDefinitionByName(getQualifiedSuperclassName(c))) ) { if (c == superClass) { return true; } } } catch(e:Error) { } return false; }
Iterating on these expensive function calls seems slow and the function itself is quite large, so skyboy
offers an alternative that takes advantage of the prototype
field and the instanceof
operator. The prototype
field is present on all valid objects and essentially tells you what made the object, which is usually its Class
. The instanceof
operator is deprecated (you’ll get a warning), but can be used for just the purposes we’re looking for: Class instanceof Class
. With some (optimized) error checking and a good catch for when the two Class
objects are the same, here is his version: (with minor editing by me)
private function isSubclassOfSkyboy(a:Class, b:Class): Boolean { if (int(!a) | int(!b)) return false; return (a == b || a.prototype instanceof b); }
Much smaller and it has no loops! So, let’s test them out to make sure they both work:
package { import flash.display.*; import flash.text.*; public class MyApp extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function MyApp() { var logger:TextField = __logger; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); log(isSubclassOfP03(int, int)); log(isSubclassOfP03(MyApp, MyApp)); log(isSubclassOfP03(MyApp, Sprite)); log(isSubclassOfSkyboy(int, int)); log(isSubclassOfSkyboy(MyApp, MyApp)); log(isSubclassOfSkyboy(MyApp, Sprite)); } private function isSubclassOfSkyboy(a:Class, b:Class): Boolean { if (int(!a) | int(!b)) return false; return (a == b || a.prototype instanceof b); } private function isSubclassOfP03(type:Class, superClass:Class): Boolean { if (superClass == Object) { return true; } try { for ( var c:Class = type; c != Object; c = Class(getDefinitionByName(getQualifiedSuperclassName(c))) ) { if (c == superClass) { return true; } } } catch(e:Error) { } return false; } } }
Here are the results showing that they all pass:
true true true true true true
Now let’s look at a little app to test for performance:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class ClassInheritance extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function ClassInheritance() { var logger:TextField = __logger; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); row("Version", "Time"); var beforeTime:int; var afterTime:int; var REPS:int = 1000000; var i:int; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { isSubclassOfSkyboy(MyApp, Sprite); } afterTime = getTimer(); row("Skyboy", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { isSubclassOfP03(MyApp, Sprite); } afterTime = getTimer(); row("player_03", (afterTime-beforeTime)); } private function isSubclassOfSkyboy(a:Class, b:Class): Boolean { if (int(!a) | int(!b)) return false; return (a == b || a.prototype instanceof b); } private function isSubclassOfP03(type:Class, superClass:Class): Boolean { if (superClass == Object) { return true; } try { for ( var c:Class = type; c != Object; c = Class(getDefinitionByName(getQualifiedSuperclassName(c))) ) { if (c == superClass) { return true; } } } catch(e:Error) { } return false; } } }
I ran this performance test with the following environment:
- Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 10.3.183.10
- 2.4 Ghz Intel Core i5
- Mac OS X 10.7.1
And got these results:
Version | Time |
---|---|
Skyboy | 64 |
player_03 | 1137 |
The performance of the skyboy
version blows away the competition by a factor of about 20x, making it the clear winner. So, next time you need to check if a Class
extends another class, keep this function in mind.
Spot a bug? Have a suggestion? Post a comment!
#1 by makc on October 3rd, 2011 ·
what’s the meaning of int(!a) stuff?
#2 by jackson on October 3rd, 2011 ·
See my response below.
#3 by lab9 on October 3rd, 2011 ·
I had a different apporach solving this problem and used describeType.
This must cost much more in performance, but this way you can also check interface implementations :
#4 by jackson on October 3rd, 2011 ·
This will definitely be more expensive as
describeType
is notoriously slow and allocates a lot of objects. However, if you need to support interfaces, it should work.Thanks for the code snippet. :)
#5 by divillysausages on October 3rd, 2011 ·
@makc It’s just checking if either class is null (correct me if I’m wrong Jackson), though it seems an interesting way to do it – surely it’s quicker to just do a normal check?
Small point that you can’t use this to check interfaces as it’s going through the prototype chain, but nice work all the same – I was looking for something like this yesterday
#6 by jackson on October 3rd, 2011 ·
That’s right, it’s a replacement for
!a || !b
to avoid the||
operator. Sadly, it can actually be faster too.Good point about the interfaces.
#7 by as3' on October 4th, 2011 ·
for interfaces, given a class to check against an interface, should not suffice:
#8 by Bob on October 3rd, 2011 ·
Interestingly, on the release projector, (int(!a) | int(!b)) is faster, but on the debug projector, (!a || !b) is faster for me.
And as a note, I think in the code of the performance test you meant ClassInheritance not MyApp.
#9 by Bob on October 3rd, 2011 ·
Sorry, I wasn’t quite clear there. Both tests were on the debug projector, I was referring to the mode they were compiled in. :/
#10 by jackson on October 3rd, 2011 ·
I think it’s a good idea to always test on the release player. This is not only because that’s what end-users will be running but also because there are cases where code A will run faster than code B in the debug player and visa versa in the release player, which is enormously frustrating. :)
For more on bitwise operator speed versus logical operator speed, see my article on the subject.
#11 by Bob on October 4th, 2011 ·
Yep, agreed. I just was being a little lazy :^)
Anyway, when I ran it on the release projector and included Bloodhound’s method:
Do you find there are ever performance swaps between plugin/projector?
#12 by jackson on October 4th, 2011 ·
Ah, that clears it up.
Yes, plugin and projector can swap as well as ActiveX and, of course, mobile. Usually it’s related to graphics or sound though, not AS3, at least with the desktop versions. Clearly x86 (desktop) chips will perform differently than ARM (mobile) chips, so you may see swaps there too.
#13 by BlooDHounD on October 3rd, 2011 ·
b.prototype.isPrototypeOf( a.prototype )
#14 by BlooDHounD on October 3rd, 2011 ·
add to test this plz
#15 by jackson on October 3rd, 2011 ·
If there is a follow-up article, I’ll be sure to add your version there.
Thanks for the suggestion.
#16 by player_03 on October 3rd, 2011 ·
For future reference: you might want to be a little more thorough when you run tests. This function would have passed the tests you ran:
I personally did some additional tests, and I can confirm that both my function and skyboy’s return false when appropriate.
I’ve tried BlooDHounD’s revision to skyboy’s function, and that works as well, though on my computer it takes about 50% longer. It’s still far better than mine, and in fact I’d recommend it over skyboy’s version, because it doesn’t produce a deprecation warning.
#17 by jackson on October 3rd, 2011 ·
I had some failing cases but removed them for brevity’s sake. :)
If there’s a follow-up article, I’ll check out BlooDHounD’s version as well as lab9’s version. As for the warning, one option is to disable it via
flex-config.xml
:Or via a compiler argument:
You may not like those options any better though. :)
#18 by elenzil on October 6th, 2011 ·
the Skyboy approach looks solid & performant.
another more language-agnostic approach i’ve used is to store the result in a cache.
unless you’re writing a compiler or something, generally the number of distinct (Class A, Class B) pairs are few.
#19 by Damian on March 9th, 2012 ·
There’s a problem with this code when you’re trying to check classes that are coming from external SWFs (linked SWC’s are fine). There are 2 ways to load a SWF in Flash, either use
Loader.load()
orURLLoader.load() then Loader.loadBytes()
. When usingLoader.load()
, both Skyboy’s and BlooDHounD’s approaches failed – only player_03’s held up. It has to do with the SWF being loaded in the wrong security domain, so if you want to use either of the first two, you can either specifySecurityDomain.currentDomain
in aLoaderContext
object (unfortunately doesn’t work in local), or go through theloadBytes()
route, which carries it’s own risks.I used this class to test if, you’re interested:
#20 by jackson on March 9th, 2012 ·
Thanks for pointing this out. Applications that dynamically load SWFs with code may want to use the slower algorithm for simplicity or both algorithms depending on the likely location of the class.