Search

Inside the Bracket, part 3 – Who Are you?

Mark Dalrymple

9 min read

Jun 12, 2013

Inside the Bracket, part 3 – Who Are you?

Want to learn more about what’s really happening inside those square brackets? Read the entire Inside the Bracket series.

Last time we took at look at some of the data you can ask an object and its class about, stuff like a the signature of a method, as well as an application using NSInvocation. This time it’s a look at some of the methods you can use to interrogate an object.

Be True to your Self

One of the simplest object interrogation methods is -self. You send it to an object, and you get that object back. I love weird little methods like this. Is there some kind of corner case that it addresses? Maybe there’s some cool technique I’m missing. Why would you have a call like this? Why go to the trouble of calling

thing2 = [thing1 self];

rather than just doing

thing2 = thing1;

One use of -self is throwing a monkey wrench into ARC. You’ve got a bug where ARC is thinking you’re done with an object, but you’re actually not, and it’s getting released from underneath you. After you file a Radar saying that ARC is mis-handling a corner case, add a call to [thing1 self] at the bottom of your method. The compiler will think you’re still using thing1 (because you now are), and the actual method is as close to a no-op as you can get.

Another use is in higher-order programming interfaces. -self is a method that takes no arguments and returns an object. A lot of other methods, like -uppercaseString, do the same thing. Say you had a function that applies a map operation to an array, where a “map operation” sends a message to every element of an array and then collects the results into another array and returns it. Here’s a cheesy implementation of a map:

NSArray *MapArrayWithSelector (NSArray *array, SEL selector) {
    NSMutableArray *map = [NSMutableArray array];
    for (id thing in array) {
        id result = [thing performSelector: selector];
        if (result) [map addObject: result];
    }
    return [map copy];  // Strip off mutability
} // MapArrayWithSelector

You can easily uppercase a set of names with it:

NSArray *names = @[ @"Emily", @"Step", @"Vikki" ];
NSArray *upperNames = MapArrayWithSelector (names, @selector(uppercaseString));

This returns an array populated with EMILY, STEP, and VIKKI. Say in your processing of data you decide that you want to pass an array through a pipeline of operations but you don’t want anything to change the data at some – just pass it on to the next stage. In that case, you can use -self as a no-op, a passthrough, without having to have any special casing:

NSArray *noOpNames = MapArrayWithSelector (names, @selector(self));

That’ll return the the list of names in their original mixed case.
Along those lines, you can use self in Key-Value Coding strings. This call is totally legal:

NSString *greeble = [@"Bork" valueForKey: @"self"]

Like with the map you can have a lot of functionality based on KVC and just use "self" when you don’t want the data to be transformed.

No, Really. Who Are You?

You can ask an arbitrary object a lot about its parentage. You can ask “are you a member of this class” with -isMemberOfClass:. You can also ask an object if it is a member of a class or a subclass of a particular class with -isKindOfClass:.

-isMemberOfClass: does an identity comparison between the receiver’s class and the class passed as an argument:

id null = [NSNull null];
if ([null isMemberOfClass: [NSNull class]]) NSLog (@"YEP!");

This prints out “YEP!”

Why am I using NSNull here, rather than a more common class like NSString? It turns out -isMemberOfClass: isn’t all that useful. Using it defeats the use of polymorphism. -isMemberOfClass: says that you don’t care about what an object can do, but that it is an instance of precisely one class.

With modern Cocoa, many of the objects you deal with on a regular basis are not actually the class you think they are, but are of a specialized subclass. Consider NSString. You’d think / hope that this would work:

if ([@"Howdy" isMemberOfClass: [NSString class]]) NSLog (@"YEP!");

But it actually doesn’t. Thanks to class clusters, the actual class of the literal string is an __NSCFConstantString. -isMemberOfClass: is asking “hey constant string, are you nothing but an NSString?” and the answer is, of course, “no”.

Instead you’d use -isKindOfClass:. This asks “Are you a member of the given class, or one of its subclasses.”. Another way of looking it is asking “Do you have this class somewhere in your inheritance chain?” Mutating the test by using -isKindOfClass: gives you good results:

if ([@"Howdy" isKindOfClass: [NSString class]]) NSLog (@"YEP!");

This will print out YEP. You’re just interested in that this thing is a string, not what particular flavor of string it is.

Checking Applications

There can be some practical applications of these kinds of check. One use of -isMemberOfClass: is in implementations of -isEqual, and you consider objects of two different classes to be unequal, even if their contents are the same:

- (BOOL) isEqual: (id) otherDude {
    BOOL equal = [self isMemberOfClass: [otherDude class]]
        && [[self name] isEqualToString: [otherDude name]]
        && [self shoeSize] == [otherDude shoeSize];
    return equal;
} // isEqual

(Just a quick style note – I like to split up the calculation and the return into two statements. It’s now very easy to put a breakpoint on the return statement and inspect what it’ll be returning.)

One use of -isKindOfClass: is querying an object you get back from a possibly insecure store. Users can muck with NSUserDefaults on the desktop, so you might get an object back that causes your program heartburn.

NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
NSDictionary *defaultDefaults = @{
   @"spoon" : @"waffle"
};
[defs registerDefaults: defaultDefaults];

It registers some defaults with NSUserDefaults. You can then later on get the value out and use it:

NSString *thing = [defs objectForKey: @"spoon"];
NSLog (@"thing is %@", [thing uppercaseString]);

You can find the complete program at this gist. What can go wrong? I put in a string, and I get out a string. On iOS it’s not that much of a problem (outside of program bugs), but on the Mac you can do all sorts of fun stuff. Here’s a run with the default value, a run after changing the default, and a final run after setting it a really bad value:

% <strong>./defaults-death</strong>
2013-06-11 15:50:13.811 defaults-death[48089:303] thing is WAFFLE
% <strong>defaults write defaults-death spoon -string badger</strong>
% <strong>./defaults-death</strong>
2013-06-11 15:51:01.548 defaults-death[48121:303] thing is BADGER
% <strong>defaults write defaults-death spoon -integer 23</strong>
% <strong>./defaults-death</strong>
2013-06-11 15:51:12.319 defaults-death[48128:303] -[__NSCFNumber uppercaseString]: unrecognized selector sent to instance 0x17c3

(and you all know what a backtrace looks like)

What happened? The “spoon” user default now has an NSNumber in it, not an NSString. You try to do stringy-things with it and things blow up in your face. You can sanity check values you get back from NSUserDefaults like this:

NSString *thing = [defs objectForKey: @"spoon"];
if ([thing isKindOfClass: [NSString class]]) {
    NSLog (@"thing is %@", [thing uppercaseString]);
} else {
    NSLog (@"bad defaults, man!");
}

Anatidae Keyboarding

In general, though, querying an object for its class is a code smell. If you’re doing object-oriented design, you should be relying on polymorphism to obviate the need to know exactly what kind of object you’re dealing with. If you have tests all over your code like “is this a fish? Do this thing. Is this a battery? Do this other thing.” then you should re-think your design. There are valid times when you might ask an object in an arbitrary collection “are you an array? Ok, I’ll process you differently than if you were just a view.”, but it’s easy to fall back on -isKindOfClass: as a crutch.

Instead of asking “are you a fish?” or “are you a battery?”, you can instead ask very specific questions like “Do you support uppercaseString?” or “Do you respond to numberOfSectionsInCollectionView:?” by using the method -respondsToSelector:.

-respondsToSelector: takes your selector, grabs the class from the object (via the isa pointer), and roots around inside the pile of methods to see if your selector is in there. If it finds the selector in there, or finds it in the set of methods that the superclasses implement, it reports success. Otherwise, not.

Objective-C doesn’t really care about the flavor of the receiver when sending a message. It just cares that it can handle a particular message by running some code. The compiler might add some type checking to catch common errors as a convenience to us, but fundamentally it’s not necessary to the operation of the language. This message agnosticism is a very powerful behavior. It enables Duck Typing.

The term duck typing comes from the phrase “if this thing walks like a duck, talks like a duck, and plays poker as badly as a duck, then I can treat it as if it were a duck.” If an object responds to a particular set of messages, you can treat it as a particular kind of object. It doesn’t really matter what the actual class (or type) the object is.

Cocoa data sources and delegates take advantage of duck typing. Any object that can handle -tableView:numberOfRowsInSection: and -tableView:cellForRowAtIndexPath: can participate as a UITableView data source. This could be one of your navigation controllers. This could be a collection of media items. You could even put a category on NSArray to make any array a possible tableview data source.

You can adopt protocols in your @interfaces and class extensions to declare to the world “Hey, I implement the required methods of this protocol. And perhaps more!”. If you’re interested in protocols, you can get more info.

What about those optional methods? If you’re implementing code that calls optional delegate or data source methods, you would use -respondsToSelector: to see if a particular object responds to that method. If it responds, groovy! Go ahead and make the call. Otherwise, don’t even bother. Checking is easy:

if ([self.delegate
         respondsToSelector: @selector(worldMap:selectedCountryCode:)]) {
    [self.delegate worldMap: self  selectedCountryCode: countryCode];
}

An aside about -conformsToProtocol:. this is a call similar to -respondsToSelector: that tells you if a class conforms to a particular protocol. You can use this to pre-flight operations. You could load a plugin and check to make sure it conforms to your application’s interface to plugins. There’s one slight gotcha – -conformsToProtocol: doesn’t actually check to make sure that the methods are implemented. All it does is see if the creator of the class explicitly adopted the protocol, not that it actually implemented everything. Is this ever a problem? Rarely. But sometimes you might be in a situation where some other programmer’s delegate object is dying when you send it messages. They might have adopted the protocol, but not actually satisfied the contract.

How is that possible? Here are two classes. Goofus says he adopts UITableViewDataSource, but doesn’t actually implement anything. Gallant, being the shy retiring type, implements the required portions of the data source, but doesn’t tell anyone:

@interface Goofus : NSObject <UITableViewDataSource>
@end
@implementation Goofus
@end
@interface Gallant : NSObject
@end
@implementation Gallant
- (NSInteger)tableView:(UITableView *)tableView
    numberOfRowsInSection:(NSInteger)section {
    return 23;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    return nil;
}
@end

The compiler might warn you about Goofus not implementing the required methods. And if you aren’t Fixing Your Warnings it can slip through. When you actually go to interact with one of these gents, the compiler will warn you that Gallant has bad mojo, but Goofus is OK:

Goofus

Then, at run time, messages to Goofus will explode in your face. HA HA.

All that being said, I believe it’s perfectly fine to use -conformsToProtocol: as a quick check to see if this object might implement all of the required methods, just don’t expect it to do a deep introspection for you. If you’re having unrecognized selector issues with @required methods, double-check to make sure the class adopting the protocol is behaving.

Up next

Remember that complaint from earlier?

-[__NSCFNumber uppercaseString]: unrecognized selector sent to instance 0x17c3

Next time we’ll take a look at what causes that, and how you can leverage unrecognized selectors to your own advantage.

Mark Dalrymple

Author Big Nerd Ranch

MarkD is a long-time Unix and Mac developer, having worked at AOL, Google, and several start-ups over the years.  He’s the author of Advanced Mac OS X Programming: The Big Nerd Ranch Guide, over 100 blog posts for Big Nerd Ranch, and an occasional speaker at conferences. Believing in the power of community, he’s a co-founder of CocoaHeads, an international Mac and iPhone meetup, and runs the Pittsburgh PA chapter. In his spare time, he plays orchestral and swing band music.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News