views:

2434

answers:

8

As a Java developer whom is pouring over Apple's Objective-C 2.0 documentation: I am in a state of wonderment as to what sending a message to nil means - let alone how it is actually useful. Taking an excerpt from the documentation:

There are several patterns in Cocoa that take advantage of this fact. The value returned from a message to nil may also be valid:

  • If the method returns an object, any pointer type, any integer scalar of size less than or equal to sizeof(void*), a float, a double, a long double, or a long long, then a message sent to nil returns 0.
  • If the method returns a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, then a message sent to nil returns 0.0 for every field in the data structure. Other struct data types will not be filled with zeros.
  • If the method returns anything other than the aforementioned value types the return value of a message sent to nil is undefined.

Has Java rendered my brain incapable of grokking the explanation above? Or is there something that I am missing that would make this as clear as glass?

Note: Yes, I do get the idea of messages/receivers in Objective-C, I am simply confused about a receiver that happens to be nil.

+7  A: 

What it means is that the runtime doesn't produce an error when objc_msgSend is called on the nil pointer; instead it returns some (often useful) value. Messages that might have a side effect do nothing.

It's useful because most of the default values are more appropriate than an error. For example,

[someNullNSArrayReference count] => 0

I.e., nil appears to be the empty array. Hiding a nil NSView reference does nothing. Handy, eh?

Rich
+20  A: 

Well, I think it can be described using a very contrived example. Let's say you have a method in Java which prints out all of the elements in an ArrayList:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

Now, if you call that method like so: someObject.foo(NULL); you're going to probably get a NullPointerException when it tries to access list, in this case in the call to list.size(); Now, you'd probably never call someObject.foo(NULL) with the NULL value like that. However, you may have gotten your ArrayList from a method which returns NULL if it runs into some error generating the ArrayList like someObject.foo(otherObject.getArrayList());

Of course, you'll also have problems if you do something like this:

ArrayList list = NULL;
list.size();

Now, in Objective-C, we have the equivalent method:

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

Now, if we have the following code:

[someObject foo:nil];

we have the same situation in which Java will produce a NullPointerException. The nil object will be accessed first at [anArray count] However, instead of throwing a NullPointerException, Objective-C will simply return 0 in accordance with the rules above, so the loop will not run. However, if we set the loop to run a set number of times, then we're first sending a message to anArray at [anArray objectAtIndex:i]; This will also return 0, but since objectAtIndex: returns a pointer, and a pointer to 0 is nil/NULL, NSLog will be passed nil each time through the loop. (Although NSLog is a function and not a method, it prints out (null) if passed a nil NSString.

In some cases it's nicer to have a NullPointerException, since you can tell right away that something is wrong with the program, but unless you catch the exception, the program will crash. (In C, trying to dereference NULL in this way causes the program to crash.) In Objective-C, it instead just causes possibly incorrect run-time behavior. However, if you have a method that doesn't break if it returns 0/nil/NULL/a zeroed struct, then this saves you from having to check to make sure the object or parameters are nil.

Michael Buckley
It's probably worth mentioning that this behavior has been the subject of much debate in the Objective-C community over the last couple of decades. The tradeoff between "safety" and "convenience" is evaluated differently by different folks.
Mark Bessey
+4  A: 

It means often not having to check for nil objects everywhere for safety - particularly:

[someVariable release];

or, as noted, various count and length methods all return 0 when you've got a nil value, so you do not have to add extra checks for nil all over:

if ( [myString length] > 0 )

or this:

return [myArray count]; // say for number of rows in a table
Kendall Helmstetter Gelner
+3  A: 

Don't think about "the receiver being nil"; I agree, that is pretty weird. If you're sending a message to nil, there is no receiver. You're just sending a message to nothing.

How to deal with that is a philosophical difference between Java and Objective-C: in Java, that's an error; in Objective-C, it is a no-op.

benzado
+5  A: 

In the quotation from the documentation, there are two separate concepts -- perhaps it might be better if the documentation made that more clear:

There are several patterns in Cocoa that take advantage of this fact.

The value returned from a message to nil may also be valid:

The former is probably more relevant here: typically being able to send messages to nil makes code more straightforward -- you don't have to check for null values everywhere. The canonical example is probably the accessor method:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

If sending messages to nil were not valid, this method would be more complex -- you'd have to have two additional checks to ensure value and newValue are not nil before sending them messages.

The latter point (that values returned from a message to nil are also typically valid), though, adds a multiplier effect to the former. For example:

if ([myArray count] > 0) {
    // do something...
}

This code again doesn't require a check for nil values, and flows naturally...

All this said, the additional flexibility that being able to send messages to nil does come at some cost. There is the possibility that you will at some stage write code that fails in a peculiar way because you didn't take into account the possibility that a value might be nil.

mmalc
+2  A: 

A message to nil does nothing and returns nil, Nil, NULL, 0, or 0.0.

Peter Hosey
+4  A: 

All of the other posts are correct, but maybe it's the concept that's the thing important here.

In Objective-C method calls, any object reference that can accept a selector is a valid target for that selector.

This saves a LOT of "is the target object of type X?" code - as long as the receiving object implements the selector, it makes absolutely no difference what class it is! nil is an NSObject that accepts any selector - it just doesn't do anything. This eliminates a lot of "check for nil, don't send the message if true" code as well. (The "if it accepts it, it implements it" concept is also what allows you to create protocols, which are sorta kinda like Java interfaces: a declaration that if a class implements the stated methods, then it conforms to the protocol.)

The reason for this is to eliminate monkey code that doesn't do anything except keep the compiler happy. Yes, you get the overhead of one more method call, but you save programmer time, which is a far more expensive resource than CPU time.

Joe McMahon
+3  A: 

ObjC messages which are sent to nil and whose return values have size larger than sizeof(void*) produce undefined values on PowerPC processors. In addition to that, these messages cause undefined values to be returned in fields of structs whose size is larger than 8 bytes on Intel processors as well. Vincent Gable has described this nicely in his blog post

Nikita Zhuk