5

If I have two classes, SubClass and SuperClass:

SuperClass *super = new SuperClass();
SubClass *sub = new SubClass();
SubClass *sub_pointer;

// **The nice one-line cast below**
sub_pointer = dynamic_cast<SubClass*> super;
// Prints NO
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO");

sub_pointer = dynamic_cast<SubClass*> sub;
// Prints YES
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO");

I can accomplish the same thing in objective-C with isMemberOfClass as follows:

SuperClass *super = [[SuperClass alloc] init];
SubClass *sub = [[SubClass alloc] init];
SubClass *sub_pointer;
id generic_pointer;

// Not as easy:
generic_pointer = super;
if ([generic_pointer isMemberOfClass:[SubClass class]]) {
  sub_pointer = generic_pointer;
} else {
  sub_pointer = nil;
}
// Logs NO
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");

generic_pointer = sub;
if ([generic_pointer isMemberOfClass:[SubClass class]]) {
  sub_pointer = generic_pointer;
} else {
  sub_pointer = nil;
}
// Logs YES
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");

Is there an easier way than this?

(P.S. I know I don't have to use the extra id variable, but then I would have to force cast super to SubClass*, which would sometimes result in an invalid reference that I would have to clean up afterwards. That implementation, however, is less wordy, and it's below)

SuperClass *super = [[SuperClass alloc] init];
SubClass *sub = [[SubClass alloc] init];
SubClass *sub_pointer;

// Not as easy:
sub_pointer = (SubClass*) super;
if (![sub_pointer isMemberOfClass:[SubClass class]]) {
  sub_pointer = nil;
}
// Logs NO
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");

sub_pointer = (SubClass*) sub;
if (![sub_pointer isMemberOfClass:[SubClass class]]) {
  sub_pointer = nil;
}
// Logs YES
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");
Douglas Mayle
  • 21,063
  • 9
  • 42
  • 57
  • `dynamic_cast` checks if the object is that class or one of its subclasses. This is different from `isMemberOfClass:` which checks for that class only. `dynamic_cast` is equivalent to `isKindOfClass:` which checks subclasses also. – user102008 May 23 '12 at 03:38

6 Answers6

4

You could add a category on NSObject to add the functionality you want.

//NSObject+DynamicCast.h
@interface NSObject (DynamicCast)
-(id)objectIfMemberOfClass:(Class)aClass;
@end

//NSObject+DynamicCast.m
@implementation NSObject (DynamicCast)
-(id)objectIfMemberOfClass:(Class)aClass;
{
  return [self isMemberOfClass:aClass] ? self : nil;
}
@end

Then you could do this:

SuperClass *super = [[SuperClass alloc] init];
SubClass *sub = [[SubClass alloc] init];
SubClass *sub_pointer;
id generic_pointer;

// **The nice one-line cast below**
sub_pointer = [super objectIfMemberOfClass:[SubClass class]];
// Prints NO
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO");

sub_pointer = [sub objectIfMemberOfClass:[SubClass class]];
// Prints YES
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO");
PeyloW
  • 36,742
  • 12
  • 80
  • 99
  • The downside here is that the return is no longer a correctly typed pointer. As Chris Suter below points out, (though i avoid them as much as possible) a macro may be better for this purpose. – jbenet Nov 25 '10 at 06:49
  • 1
    @jbennet: I would not waste to much time trying to add typesafety at runtime to a dynamic language such as Objective-C. It is against the fundamental philosophy of the language. Instead make sure to have allot of unit tests that verifies the correctness of your model code. This also have the extra added bonus of catching errors every time you build, not when the user runs the app in the wild, which is too late anyway. – PeyloW Nov 25 '10 at 14:29
  • @jbenet More recent versions of xCode provide `instancetype` which allows an improved [type safer variant](http://stackoverflow.com/a/26803004/2547229) that I've added an answer for. – Benjohn Nov 07 '14 at 14:05
4

I use a macro:

#define DYNAMIC_CAST(x, cls)                                \
  ({                                                        \
    cls *inst_ = (cls *)(x);                                \
    [inst_ isKindOfClass:[cls class]] ? inst_ : nil;        \
  })

I marginally prefer it to using a category on NSObject because the returned object is the correct type (rather than id), although I realise that in most cases you’re simply going to assign it to a variable of the same type anyway.

Chris Suter
  • 2,897
  • 2
  • 19
  • 10
  • Hey Chris, is there a reason you use the extra `cls *inst_ = (cls *)(x);` ? Why not just: `[x isKindOfClass:[cls class]] ? (cls *)x : nil;` ? – jbenet Nov 25 '10 at 06:44
  • 3
    @jbenet—Yes. This is a standard gotcha to watch out for in macros. In your case, x will be evaluated twice, so if you were to use the macro like this: `DYNAMIC_CAST(someFunc(), SomeClass)`, `someFunc` would be called twice which is not what you want. – Chris Suter Dec 06 '10 at 11:17
2

If you're allowed to throw any C++ into the mix you can avoid macros and get the correct type with a template routine:

template <typename T, typename U>
inline T* objc_cast(U* instance)
{
    return [instance isMemberOfClass:[T class]] ?
               static_cast<T*>(instance) :
               nil;
}

Then the call would simply look like:

sub_pointer = objc_cast<SubClass>(super);
fbrereto
  • 35,429
  • 19
  • 126
  • 178
1

More recent versions of xCode provide instancetype and allow a very neat category based solution. Using it looks like this:

TypeIWant *const thingIWant = [TypeIWant tryCast: thingIWantToCast];

Category Declaration on NSObject

@interface NSObject (DynamicCast)
// Try a dynamic cast. Return nil if the class isn't compatible.
+(instancetype) tryCast: (id) toCast;

// Check a dynamic cast. Throw if the class isn't compatible.
+(instancetype) checkCast: (id) toCast;
@end

Category Implementation

@implementation NSObject (DynamicCast)

+(instancetype) tryCast: (id) toCast
{
    return [toCast isKindOfClass: self] ? toCast : nil;
}

+(instancetype) checkCast:(id)toCast
{
    const id casted = [self tryCast: toCast];
    if(!casted)
    {
        [NSException raise: NSInvalidArgumentException format: @"Can't cast %@ to be an %@", toCast, NSStringFromClass(self)];
    }
    return casted;
}

@end

Usage

This is a class method callable on any class having NSObject as a super class (so, basically, any class).

You send the tryCast: method to the class you hope to cast to, with the object you want casted as the parameter. Like this [ClassIWant tryCast: thingIWantCasted].

Example Of Use

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UIPopoverController *const popoverDestination =
    [UIStoryboardPopoverSegue tryCast: segue].popoverController;
    if(popoverDestination)
    {
        UITableViewCell *const tableViewSender =
        [UITableViewCell tryCast: sender];
        if(tableViewSender)
        {
            // Things you need to do in this case.
            ...
Benjohn
  • 13,228
  • 9
  • 65
  • 127
0

It occurs to me that I can use the ternary operator to put this all on one line, but it's still a bit of a mess:

SuperClass *super = [[SuperClass alloc] init];
SubClass *sub = [[SubClass alloc] init];
SubClass *sub_pointer;

// One line, but still a bit wordy
sub_pointer = [super isMemberOfClass:[SubClass class]] ? (SubClass*) super : nil;
// Logs NO
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");

sub_pointer = [sub isMemberOfClass:[SubClass class]] ? (SubClass*) sub : nil;
// Logs YES
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");

If I'm getting the intended variable back from a function, I'll have to cache it in an id variable for this version to work.

SubClass *sub_pointer;
id generic_pointer;

// One line, but still a bit wordy
generic_pointer = (id) mySuperFunc();
sub_pointer = [generic_pointer isMemberOfClass:[SubClass class]] ? generic_pointer : nil;
// Logs NO
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");

generic_pointer = (id) mySubFunc();
sub_pointer = [generic_pointer isMemberOfClass:[SubClass class]] ? generic_pointer : nil;
// Logs YES
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO");
Douglas Mayle
  • 21,063
  • 9
  • 42
  • 57
  • Well, you could now take that and make it a function. `id dynCast(Class subClass, id object)`. You probably want `isKindOfClass:` over `isMemberOfClass`. You may think you know what your classes are, but something like KVO may dynamically subclass and change the class of your objects. – Ken Sep 03 '09 at 15:54
  • The whole point of dynamic_cast is to figure out when you have the subtype specifically (so that you can safely call subtype-specific methods). isKindOfClass would also give me sub-sub-types, which I may or may not want depending on the circumstance. That being said, I haven't gotten yet to KVO, so I may be speaking out of my behind.... – Douglas Mayle Sep 03 '09 at 16:06
  • I'm saying you want sub sub types too, irrespective of what C++ does. :-) Sub sub types still respond to sub-type specific messages, and Cocoa is too dynamic to be able to count on getting _exactly_ a particular subclass, even when you wrote the subclass. – Ken Sep 03 '09 at 16:13
  • 3
    I should also mention: What you're doing is valid, but not commonly done. It'd be more Cocoa-y to use `respondsToSelector:` to check for functionality, not try to identify something as a subclass. – Ken Sep 03 '09 at 16:17
  • Hi, just wanted to say that i thing you have something wrong going on here.. you say that.. "If I'm getting the intended variable back from a function, I'll have to cache it in an id variable for this version to work." Why do you think that? Casting a pointer never alters the class of an object.. –  Sep 03 '09 at 16:40
  • OOps, i think i get what you mean. sub_pointer = [mySubFunc() isMemberOfClass:[SubClass class]] ? mySubFunc() : nil; would work tho –  Sep 03 '09 at 16:53
0

basically..

id sub_pointer = [foo isMemberOfClass:AClass] ? foo : nil;
NSLog(@"Is a subclass: %i", sub_pointer!=nil ); 

doesn't seem alot more wordy.

  • Yes, but sub_pointer is no longer a typed pointer, and if foo is a method, you have to call it twice, or use an additional storage mechanism. – Douglas Mayle Sep 04 '09 at 12:05