Katedral Pillon
Katedral Pillon

Reputation: 14864

How to compare two numbers in iOS Objective-C

What is the safe way to compare to NSNumbers? If I do isEqualToNumber: and one of the numbers is nil, I still get an

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber compare:]: nil argument'

I thought nil defaulted to zero in iOS? Please help me understand some fundamentals if possible. Thanks.

my code is

numberB=[numberB isKindOfClass:[NSNull class]]?0: numberB;
if([numberA isEqualToNumber: numberB]){...}

It seems that the NSNull check is ignored because my logging shows that numberB is still (null) instead of 0. I am really trying, here. So thanks for any help.

Upvotes: 2

Views: 2090

Answers (6)

TotoroTotoro
TotoroTotoro

Reputation: 17622

Nil defaults to zero (or the boolean NO) in logical statements. So for example if(nilObject) will evaluate to false. Nil arguments by default do not evaluate as 0. It's up to the specific method's implementation.

In this case, NSNumber compare: expects the argument to be non-nil. It's not a universal rule in Objective-C, just an NSNumber implementation detail.

You could first convert your NSNumber objects into primitive types, e.g. by calling [myNumber doubleValue] and then compare those. By the way, getting the primitive value from a nil NSNumber will give you 0.

Upvotes: 1

Toastor
Toastor

Reputation: 8990

While the other answers are correct, let me add some more basic explanation:

In programming we're dealing with values and pointers. A value is a piece of data such as a numeric value or a string of characters. A pointer is a value too, but a specialized one - it is the address of a value in memory. Hence the name - it points to a piece of usable data.

If you do something like NSNumber *happyNumber = @7;, happyNumber is a pointer and it points to the value 7. Well, not exactly, because your 7 is actually wrapped in an object, but that's another story.

Now, sometimes a pointer is still around while the value it pointed to became invalid or is about to. Or the value doesn't even exist yet. But a pointer can never be "empty" and will always point at some memory address, so using such a pointer may result in undefined and random behavior. And to prevent this, we're using nil to give the pointer something meaningful to point to. It is sort of a placeholder, representing the "empty" state for a pointer and at the same time makes accidential use of the pointer relatively safe.

This means that you can throw messages at a nil object and quite literally, nothing happens. If you feed a nil pointer or object to another object though, it might not be prepared or able to handle "emptiness". This is what happened with isEqualToNumber:: it makes no sense to compare two numbers if one isn't even a number. In this case even just returning a boolean NO would be wrong in a way too, because it would imply that we are dealing with two perfectly healthy numbers which just happen to be different. So instead, NSNumber resolves the situation by throwing an exception.

NSNull is similar to nil in that it represents absence of something useful, however it is not the same. NSNull is a class and you can get a (singleton) instance of it. It is used where the absence of something needs to be represented by an actual, live object.

That's why the first version of your ternary operation failed - numberB was nil, not [NSNull null] so you where just checking for the wrong type of "emptiness" (@0 results in a perfectly fine NSNumber, not nil).

You can of course check for both possibilities, but if you default on using nil for yourself (which you should) this will seldom be necessary.

Hope that helps. Cheers!

Upvotes: 0

Katedral Pillon
Katedral Pillon

Reputation: 14864

I finally got it to work by doing

numberB= (numberB == nil)?[NSNumber numberWithLongLong:0]:numberB;

So the problem seems that @0 or simple 0 was being converted to nil. I don't quite understand it, but now it works. Thanks and +1 for everyone for all the help.

Upvotes: 0

rob mayoff
rob mayoff

Reputation: 385660

If the argument might be nil, use isEqual:, not isEqualToNumber:. The isEqual: method is documented to accept a nil argument.

What defaults to nil (or a zero-like quantity) in Objective-C is the return value of a message when you send the message to nil. For example, [nil isEqualToNumber:@7] returns NO; [nil copy] returns nil; [nil integerValue] returns 0. Thus it is generally safe to send any message to nil. It is not necessarily safe to pass nil as an argument to a message if the message isn't documented to accept nil as an argument.

UPDATE

Based on the code you added, you could do this:

numberB = (numberB == [NSNull null]) ? @0 : numberB;
if ([numberA isEqualToNumber:numberB]) {
    ...
}

Note that @0 represents an NSNumber with value 0. Also, there is only one instance of NSNull, so you can check for it with ==.

UPDATE 2

If NSLog is printing (null), then numberB is nil, which is different that [NSNull null]. You can check for both possibilities like this:

numberB = (numberB == nil || numberB == [NSNull null]) ? @0 : numberB;
if ([numberA isEqualToNumber:numberB]) {
    ...
}

Upvotes: 2

Duyen-Hoa
Duyen-Hoa

Reputation: 15784

NSLog for numberB is null cause numberB is nil.

Make a test:

NSLog(@"NumberB is nil? %@", numberB == nil ? @"YES":@"NO"); --> displays 'NumberB is nil? YES'

when you compare a nil to [NSNull null], it returns NO

I suppose that you get numberB from a code like:

NSNumber *numberB = (NSNumber*)[[NSUserDefaults standardUserDefaults] valueForKey:@"numberB"];

it returns 'nil' value. you should then compare numberB to nil, but not to [NSNull null].

Example

numberB = numberB != nil? numberB : @0;
if ([numberA isEqualToNumber:numberB) {
...
}

Upvotes: 1

Krys Jurgowski
Krys Jurgowski

Reputation: 2911

If you are just worried that numberB could be nil and want to ignore that case:

if([numberA isEqualToNumber:numberB?: @(NSIntegerMax)]){...};

Note that this will also ignore any case where numberA is nil.

Upvotes: 1

Related Questions