"What is it" vs. "What does it do" vs. "Just do it"

On the Objective-C mailing list, Patrick Collins asked the following:

I am wondering, what is the difference between testing if an instance responds to a method, and checking the kind of class it is?

I mean, I understand that if you had 10 classes that all have a method, and one that doesn't, it's easier to just do [myClass respondsToSelector:@selector(foo)], rather than doing [myClass isKindOfClass:[Thing1ThatHasMethod class]] || [myClass isKindOfClass:[Thing2ThatHasMethod class]] || …etc

But, I have a situation where an NSDictionary is holding an object that is either an NSNumber or an NSArray of NSNumbers.. And I wondered which of these is the best way to go?

  if ([item isKindOfClass:[NSArray class]])

vs.

  if ([item respondsToSelector@selector(objectAtIndex:)])

?

Here's my personal roundup of how I use the approaches that were discussed in response to Patrick's question.

isKindOfClass:

I would use isKindOfClass: for the situation Patrick (the OP) described. You already have specific expectations that the dictionary entry will be either a number or an array (assuming your data isn't corrupted). You want to know which it is, so you ask it. To me, this maps most closely to my mental model of the situation.

One scenario where I've used isKindOfClass: is when getting values from NSUserDefaults. I may have changed the app from allowing just one value for a user preference to multiple values, so I could reasonably expect to get either an NSNumber or an NSArray.

respondsToSelector:

I typically use respondsToSelector: in these scenarios:

  1. I'm deciding whether to send a particular delegate message to my delegate, and there's no hint in the declaration of the delegate that lets me assume it implements the method. For example, the delegate might be declared as a plain id, so I have no idea at all what it implements. Or it might be declared as conforming to a delegate protocol, but that particular method might be @optional. Note that I'm in this scenario when I'm creating the class that does the delegating — that's the class that has to decide whether to send the message.

  2. Occasionally in an action method I'll sanity-check the "sender" argument. I may be expecting the sender to respond to -representedObject (and not caring whether it's because it's an NSMenuItem or an NSButtonCell or whatever), so I check for that.

  3. Occasionally I do something similar to the responder chain where I look for an object on some chain that responds to the message, and if I find one, I send the message.

In all these scenarios the question is "I have this message I might want to send, and I want to know whether and to what object I should send it."

conformsToProtocol:

I don't think I've ever used conformsToProtocol:. I don't have a cogent explanation for why not. It's just never seemed to be the real question I wanted to ask.

Category methods

By adding category methods, you can let the inheritance mechanism do the "if this kind of object, then do that", which is after all what it's designed for. Ondra Čada gave an example:

@interface NSObject (MySum)
-(int)mySum;
@end
...
@implementation NSNumber (MySum)
-(int)mySum { return self.intValue; }
@end
@implementation NSObject (MySum)
-(int)mySum { int s=0; for (id o in self.objectEnumerator) s+=[o mySum]; return s; }
@end

Mike Ash gave a variation which is the logical equivalent of isKindOfClass: in this special case:

@interface NSObject (FooChecking)
- (BOOL)ma_isFoo; // prefixed to avoid conflict
@end
 
@implementation NSObject (FooChecking)
- (BOOL)ma_isFoo { return NO; }
@end
@implementation Foo (FooChecking)
- (BOOL)ma_isFoo { return YES; }
@end

Smalltalk takes a similar approach with methods like isNumber and isSymbol, which every Smalltalk object responds to.

I like the category approach and have used it on occasion, especially where (a) I'm doing the check in a lot of places, and/or (b) I feel it expresses my question better. I am lazy enough that I'll just do an isKindOfClass: if it's only in one place.

Even with the isFoo approach you'll probably be casting to Foo* at some point, so you're still "cheating" in that you're using knowledge of an object's specific class.

Leave a Reply

Your email address will not be published. Required fields are marked *