« Building pstree 2.26 on Mac OS X 10.3.6 | Main | Upgrading to Subversion 1.1.2 »
December 14, 2004
Messaging nil
I often use Objective-C's ability to send messages to nil to help make my source code more readable. But today I discovered a bug in WinSwitch that grew out of this habit and caught me off guard. This article describes the bug in the hopes that others won't fall prey to it in the future.
The idea
Basically, the idea is that when you send a message to nil, the result will be nil. This allows you to write things like the following:
NSString *name = [[prefsDictionary objectForKey:@"Owner"] stringValue];
Instead of the more complicated:
NSString *name = nil; if (prefsDictionary) name = [[prefsDictionary objectForKey:@"Owner"] stringValue];
In the first example you don't have to worry about whether the preferences dictionary exists (maybe this is the first time the user has run the program and the preferences plist hasn't been written to disk yet) or whether that particular key is present in the dictionary (maybe the user has accepted the default value and so the setting never ends up in the dictionary). You just know that if the preference is set, then you'll get the name; otherwise, you'll get nil. But either way the behaviour is nice and predictable.
The bug
I noticed that WinSwitch was showing the user icons in the menu bar at the wrong size instead of the default whenever no preferences were set. I peppered my code with some NSLog statements to find out what was actually happening, and made a test project to isolate the problem. Here is an example of code to reproduce the fault:
float aFloat; NSDictionary *prefs = nil; aFloat = [[prefs objectForKey:@"Height"] floatValue]; NSLog(@"Float is %f", aFloat);
In the example above I purposely provide a nil dictionary just to demonstrate what happens when the user preferences aren't set. I would expect aFloat to be 0.0, but it wasn't; it was returning insane values, and not always the same ones! At first I thought I had discovered a bug in GCC or in the Objective-C runtime itself. Then I found this page in the Apple documentation, which says:
"A message to nil also is valid, as long as the message returns an object; if it does, a message sent to nil returns nil. If the message sent to nil returns anything other than an object, the return value is undefined."
And there is your answer. The "floatValue" method returned something other than an object, so the return value was undefined. This one caught me by surprise, because my experience with other kinds of non-object return values (such as ints and BOOLs) had always behaved exactly as you would expect. For example:
NSObject *nothing = nil; int anInt = [nothing intValue]; // returns 0 BOOL aBOOL = [nothing boolValue]; // returns NO (0)
This makes perfect sense to me, because a message to nil is really a message to (id)0. It makes sense that it should return 0, coerced appropriately: 0 for ints, 0.0 floats, NO for BOOLs, and so forth. But it is not so, and the documentation does make note of it. Now I have to look back over all the code I've ever written and try to find places where I rely on Objective-C's ability to send messages to nil, and make sure that I'm only using that behaviour in cases where an object gets returned. Not an easy task!
Posted by wincent at December 14, 2004 09:34 PM