somtingwong
somtingwong

Reputation: 359

Objective C ternary operator vs if statements nil check

Im checking to see if an array is nil and if it is I want to set some counter variable to 0 and to the length of the array if the array is not nil.

NSUInteger count = 0;
if (self.someArray == nil) {
    count = 0;
}
else {
    count = self.someArray.count;
}

and this works fine just as expected. However when I try to do something like this:

NSUInteger count = (self.someArray == nil) ? 0 : self.someArray.count

I get an [NSNull count] unrecognized selector error. However, when I do this

([self.someArray isKindOfClass:[NSNull class]]) ? 0 : self.someArray.count` 

It seems like the latter should work to me but is there something simple that I am overlooking? NSNull is for objects that are nil right? Why is the simple nil check working in the if statement and not in the ternary operator?

Upvotes: 0

Views: 1362

Answers (2)

danh
danh

Reputation: 62686

nil is really (void *)0. NSNull is a class that has a single instance which you can get with [NSNull null]. It is legal and harmless to send messages to nil, but messaging null is subject to run-time type checking.

So lets initialize an array to the NSNull object. Any code that starts out this way is sure to cause sadness at runtime, but this is exactly what's happening when you deserialize JSON (it has to do something with nulls found in the JSON)...

NSArray *array = (NSArray *)[NSNull null];  // evil cast, just to illustrate

if (array == 0)
    NSLog(@"I will never execute");
else
    NSLog(@"I will always execute.  null is a pointer to the instance of NSNull");

if ([array isKindOfClass:[NSNull class]]) // true, of course
if (array == [NSNull null]) // true, null is a singleton

if (!array)        // false, same as above
if (array == nil)  // false, same as above, array points to an object

NSLog(@"CRASH on next line, because null doesn't respond to count");
if (array.count > 0) // crash

But if we try the some thing starting with the more benign nil...

NSArray *array = nil;
// or NSArray *array;  same thing, since compiler will init to 0x0 for you

if (array == 0)
    NSLog(@"I will always execute");
else
    NSLog(@"I will never execute");

if (!array)        // true, same as above
if (array == nil)  // true, of course

NSLog(@"No crash on next line");
if (array.count > 0)
    NSLog(@"I will never execute");

That last bit is interesting. We can send the count message to a nil array without penalty. It answers 0, as if the array is empty. What's really happening is that we can send any message to nil, and the result will always be nil.

So what to do about your problem? One idea is to accept that self.array can sometimes be == [NSNull null] (which, again, is not the same as == nil). The better solution is to catch the JSON-produces-NSNull condition at the point of assignment, like this...

// I just finished deserializing some JSON to id someObject
self.someArray = (someObject.theArray == [NSNull null])? nil : someObject.theArray;

// anytime after, we can safely message the array
NSInteger theCount = self.someArray.count;  // zero, if someArray is empty OR nil
// and even get objects from it, as long as it contains any
id someObject = (self.someArray.count)? [someArray lastObject] : nil;

Upvotes: 1

trojanfoe
trojanfoe

Reputation: 122391

self.someArray is an NSNull object, not an NSArray object.

I suspect you got the object from a JSON message and haven't checked its type before storing it in your class.

Once you have that sorted; this works if the array is set or not:

NSUInteger count = self.someArray.count;

Upvotes: 1

Related Questions