Search code examples
iphoneobjective-ccocoa-touchnsnumber

check if NSNumber is empty


How do I check if a NSNumber object is nil or empty?

OK nil is easy:

NSNumber *myNumber;
if (myNumber == nil)
    doSomething

But if the object has been created, but there is no value in it because an assignment failed, how can I check this? Use something like this?

if ([myNumber intValue]==0)
   doSomething

Is there a general method for testing objects on emptiness like for NSString available (see this post)?

Example 1

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:@"" forKey:@"emptyValue"];
NSNumber *emptyNumber = [dict objectForKey:@"emptyValue"];

Which value does emptyNumber contain? How can I check if emptyNumber is empty?

Example 2

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:@"" forKey:@"emptyValue"];
NSString *myString = [dict objectForKey:@"emptyValue"];
if (myString == nil || [myString length] == 0)
    // got an empty value
    NSNumber *emptyNumber=nil;

What happens if I use this after emptyNumber was set to nil?

[emptyNumber intValue]

Do I get zero?

Example 3

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:@"" forKey:@"emptyValue"];
NSNumber *myEmptyValue = [dict objectForKey:@"emptyValue"];
if (myEmptyValue == nil)
    // NSLog is never called
    NSLog(@"It is empty!");

Like this way NSLog is never called. myEmptyValue is not nil and not NSNull. So it contains an arbitrary number?


Solution

  • NSValue, NSNumber, ... are supposed to be created from a value and to always hold one. Testing for a specific value like 0 only works if it isn't in the range of valid values you are working with.

    In the rare case where code is more straight-forward to work with if you have a value that represents "invalid" or "not set" and you can't use nil (e.g. with the standard containers) you can use NSNull instead.

    In your first example this could be:

    [dict setValue:[NSNull null] forKey:@"emptyValue"];
    
    if ([dict objectForKey:@"emptyValue"] == [NSNull null]) {
        // ...
    }
    

    But note that you can simply not insert (or remove) that value unless you need to differentiate nil (i.e. not in the container) and, say, "invalid":

    if ([dict objectForKey:@"nonExistent"] == nil) {
        // ...
    }
    

    As for the second example, -intValue gives you 0 - but simply because sending messages to nil returns 0. You could also get 0 e.g. for a NSNumber whose intValue was set to 0 before, which could be a valid value.
    As i already wrote above, you can only do something like this if 0 is not a valid value for you. Note the for you, what works best completely depends on what your requirements are.

    Let me try to summarize:

    Option #1:

    If you don't need all values from the numbers range, you could use one (0 or -1 or ...) and -intValue / ... to specifically represent "empty". This is apparently not the case for you.

    Option #2:

    You simply don't store or remove the values from the container if they are "empty":

    // add if not empty:
    [dict setObject:someNumber forKey:someKey];    
    // remove if empty:
    [dict removeObjectForKey:someKey];
    // retrieve number:
    NSNumber *num = [dict objectForKey:someKey];
    if (num == nil) {
        // ... wasn't in dictionary, which represents empty
    } else {
        // ... not empty
    }
    

    This however means that there is no difference between keys that are empty and keys that never exist or are illegal.

    Option #3:

    In some rare cases its more convenient to keep all keys in the dictionary and represent "empty" with a different value. If you can't use one from the number range we have to put something differently in as NSNumber doesn't have a concept of "empty". Cocoa already has NSNull for such cases:

    // set to number if not empty:
    [dict setObject:someNumber forKey:someKey];
    // set to NSNull if empty:
    [dict setObject:[NSNull null] forKey:someKey];
    // retrieve number:
    id obj = [dict objectForKey:someKey];
    if (obj == [NSNumber null]) {
        // ... empty
    } else { 
        // ... not empty
        NSNumber *num = obj;
        // ...
    }
    

    This option now allows you to differentiate between "empty", "not empty" and "not in the container" (e.g. illegal key).