check if NSNumber is empty
Asked Answered
T

5

19

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?

Tyne answered 15/9, 2010 at 10:4 Comment(2)
@[0.7] will give an NSNumber that is definitely not zero, but intValue will return 0. So that's a bad test.Tychon
Upvoted for the great detail which helped explain something I'm dealing with from defaults. NSNumber* savedPin = [[NSUserDefaults standardUserDefaults] valueForKey:@"someKey"]; returns an object which has class __NSCFConstantString and thus comparisons to nil were failing. THANKS!Coaster
M
14

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).

Miscarry answered 15/9, 2010 at 10:14 Comment(8)
I edited my post. How can I test on emptiness in this "special case"? Is it in the range of valid values here?Tyne
You are inserting [NSNull null] to test later a NSNumber object on emptiness. OK, but what are standard containers? I cannot guarantee which value is assigned to my NSNumber object. So I don't know when to insert nil or NSNull. In my example it could also be an empty string. Should I first test the return value of objectForKey if it is an empty string and then I should insert nil or NSNull in my NSNumber object? I think I should always insert nil, unless I need NSNull for some case. See my second example.Tyne
@testing: Standard containers are those from Cocoa Touch (NSArray, NSDictionary, ...). The first sample tests for the object being NSNull, the second assumes you don't put the value in the container - you can't insert nil in those, nil represents "not in the container". You simply have to decide wether you have to differentiate between "invalid" / "not existent" and "not in the container". Your last sentence is close to what i say - either don't put it into the container (so you get nil for -objectForKey:...) or go with NSNull.Miscarry
@testing: Also as for "So I don't know when to insert nil or NSNull" - you have to know somewhere, the objects do not magically set themselves to a specific value. Either you do know in some place in your code whether you are working with "invalid" or "empty" values or you are doing it wrong.Miscarry
@Georg: So the problem of understanding was the following: my English; nil is not allowed to be used in NSArray/NSDictionary/...; missing knowledge of the difference between nil and NSNull; from the dictionary I get an unknown return value so I started mixing NSNumber with other things. Questions: 1) nil and NSNull together in a container doesn't work, because the meaning is different but the checking if nil leads to the same result? 2) Do you mean "can insert nil in those"?Tyne
@Georg: I do use 0 so I cannot check if it is zero. I use "not in the container" only if I have an Array for example where I can't put a nil value in it. So the solution for me is: Use isEmpty from this question. You have to convert to another data type to check if the unknown return value is of length zero (done by isEmpty or like in example 2). NSNumber is checked with nil (for the standard containers check if NSNull)Tyne
@testing: 1) If e.g. -objectForKey: returns nil it means that there is no such entry, see the docs for NSDictionary, etc. I've tried to summarize the options in the answer, i hope that makes it clearer. 2) Nope, see 1). I don't know why you always want to use strings to represent "empty", that's not how to do it. I recommend to read through the collections guide and read through the documentation for the classes you use to clear up some misconceptions.Miscarry
Thanks for summarizing it! Now it's much more obvious. Didn't thought about objectForKey: is also returning nil. In my sample code it could happen that I get an empty string. So I wanted to check that. If the value is not empty, it is a NSNumber. That's why I wanted to know, which value NSNumber has. But we have to pay attention the the initial question: How to check if a NSNumber is empty. And the answer is nil or it contains a number.Tyne
H
3

NSNumber is either nil, or it contains a number, nothing in between. “Emptiness” is a notion that depends on the semantics of the particular object and therefore it makes no sense to look for a general emptiness check.

As for your examples, there are several things going on:

NSMutableDictionary *hash = [NSMutableDictionary dictionary];
[hash setObject:@"" forKey:@"key"];
NSNumber *number = [hash objectForKey:@"key"];
NSLog(@"%i", [number intValue]);

The NSLog will print 0 here, but only because there’s an intValue method in NSString. If you change the message to something that only NSNumber can do, the code will fail:

NSLog(@"%i", [number unsignedIntValue]);

This will throw:

-[NSCFString unsignedIntValue]: unrecognized selector sent to instance 0x303c

Which means you are not getting some general “empty” value back from the hash, you just get the NSString you stored there.

When you have an empty (== nil) NSNumber and send it a message, the result will be zero. That’s simply a language convention that simplifies code:

 (array != nil && [array count] == 0)
 (someNumber == nil ? 0 : [someNumber intValue])

Will turn into this:

 ([array count] == 0)
 ([someNumber intValue])

Hope this helps.

Hynes answered 15/9, 2010 at 10:13 Comment(3)
So what does NSNumber *test = [NSNull null] do?Sylphid
As written, that code does not make much sense. NSNull is just an “object version of the nil value” and about the only reason it exists is to represent an “empty” value in collections that can’t store nil directly.Hynes
Thanks, yeah, I guess I'm just trying to be clear in my mind exactly what range of possibilities I need to consider for NSNumberSylphid
F
1

Dealing with Swift 2.0 and Parse:

var myNumber = yourArray!.objectForKey("yourColumnTitle")
    if (myNumber == nil) {
    myNumber = 0
      }

In my case, I then had to:

    let myNumberIntValue = myNumber!.intValue
Frustum answered 8/12, 2015 at 21:41 Comment(0)
R
0

NSNumbers are immutable and can only be created with either a factory method or initial method that gives them some numeric value. As far as I know it is not possible to end up with an 'empty' NSNumber, unless you count 0.

Robledo answered 15/9, 2010 at 10:15 Comment(0)
D
0

It's very common (for me at least) to get an object out of a dictionary, expect that it's going to be an NSNumber and then have it return a nil object. If this happens and you do an intValue it will crash.

What I do is setup nil protection because I'd rather get a default value than a crash.

One way is:

-(int) intForDictionary:(NSDictionary *)thisDict objectForKey: (NSString *)thisKey withDefault: (int)defaultValue
{
    NSNumber *thisNumber = [thisDict objectForKey:thisKey];
    if (thisNumber == nil) {
        return defaultValue;
    }
    return [thisNumber intValue];
}

And I have one just like it for floats. Then you at least get your default value. Another way is to just create a method for nil protection..

-(NSNumber *)nilProtectionForNumber: (NSNumber *)thisNumber withDefault: (NSNumber *)defaultNumber
{
    if (thisNumber) {
        return thisNumber;
    }
    else
        return defaultNumber;
}

That one you'd call like this:

NSNumber *value = [self nilProtectionForNumber:[dict objectForKey:keyThatShouldBeNSNumber] withDefault:[NSNumber numberWithInt:0]];
Demarcation answered 21/6, 2012 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.