Friday, 11 February 2011

[ios] Dynamic cast in Objective-C

In C++, you can cast a pointer between two (related) types, and ask the runtime to check if the conversion is valid using dynamic_cast. Usually in C++ the use of dynamic_cast is a sign that you're doing something wrong - good C++ design generally avoids such constructs.

However, Objective-C is a far more dynamic language, and often you do need to do something similar. The runtime provides all the smarts for this, but doesn't deliver it in a simple one-liner.

So let's fix that...

Wondering if a view controller's view property is a UISwitch? Try this:
UISwitch *switch
= objc_dynamic_cast(UISwitch,viewController.view);
if (switch) NSLog(@"It jolly well is!);
That's nice, isn't it? Here's how:
#define objc_dynamic_cast(TYPE, object) \
({ \
TYPE *dyn_cast_object = (TYPE*)(object); \
[dyn_cast_object isKindOfClass:[TYPE class]] ? dyn_cast_object : nil; \
})

For the curious, here's what's going on...

First, we define a preprocessor macro using a gcc extension: the macro compound statement. That's the ({ ... }) block. This is a little dirty, and I certainly wouldn't use a gcc-specific extension in my "portable" C++ code. However, Objective-C lives in a different ecosystem, where you can guarantee that compound statements will be supported. So we'll go with it.

The reason I use a compound statement is to define a "local" variable - dyn_cast_object. This is just to cover the macro's back - it needs to mention the name of the object argument twice, and if that expression has side effects you'd get twice the number of side effects otherwise. Side-side effects. So I store the argument in a local variable to avoid this. (Most of the time this is unlikely to be an issue, but if you ever got bit by this problem, you'll appreciate having done it right the first time).

Then we simply ask the object is it is of the class of our cast's destination type, using NSObject's isKindOfClass: selector.

Not too complex. And you could do the isKindOfClass: dance wherever you need to cast. But it's nice to write the macro once and then write clear code that reveals it's intent.

No comments: