tags:

views:

585

answers:

4

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");
A: 

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
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
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
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
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
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..
OOps, i think i get what you mean. sub_pointer = [mySubFunc() isMemberOfClass:[SubClass class]] ? mySubFunc() : nil; would work tho
Yeah, you have to call the function twice...
Douglas Mayle
+1  A: 

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
+1  A: 

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
A: 

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