12

I've written a large social networking iPhone application, and one of the biggest issues I run into is the fact that NSInteger (and all the other NS-non-object types) are not first class citizens. This problem stems from the fact that, obviously, they have no representation for a nil value.

This creates two main problems:

  1. Tons of overhead and opaqueness to convert to and from NSNumber when storing/retrieving from a collection.
  2. Can't represent nil. Oftentimes, I want to be able to represent an "unset" value.

One way to solve this is to use NSNumber all the time, but that gets extremely confusing. In a User model object, I would have about 20 different NSNumbers, and no easy way to tell if each one is a float, integer, bool, etc.

So here are my thoughts for potential solutions and the pros/cons. I'm not really sold on any of them, so I thought I'd ask for feedback and/or alternative solutions to this problem.

  1. Continue to use NSInteger types, and just use NSIntegerMax to represent nil.
    PRO - Less memory overhead
    PRO - Clear typing
    CON - NSIntegerMax is not really nil. If programmers aren't careful or don't know this convention, invalid values could leak into the display layer.
    CON - Can't store them in a collection without conversions in and out

  2. Use NSNumber and designate types using hungarian notation (eg NSNumber fHeight, NSNumber iAge)
    PRO - First-class citizens
    PRO - Nil problem solved
    CON - Increased memory overhead
    CON - Lose compiler type checking
    CON - Hungarian notation is contentious

  3. Write my own first-class primitive object types (think Java http://developer.android.com/reference/java/lang/Integer.html)
    PRO - First-class citizens
    PRO - Nil problem solved
    PRO - Keeps compiler type checking
    PRO - Objects will be simpler than NSNumber. Internal storage will specific to data type.
    CON - Increased memory overhead
    CON - Sacrifices a bit of code portability and compatibility

Looking for a convincing argument in favor of one of these techniques, or one I haven't thought of if you've got one.


UPDATE

I've gone ahead and started an open source project (Apache 2.0), into which I'll be pulling a number of our internal classes as I have time. It currently includes object wrappers for some of the more common native data types (BOOL, CGFloat, NSInteger, NSUInteger). We chose to do this because it upgrades these data types to first class citizens with strict typing. Maybe you disagree with this approach, but it has worked well for us, so feel free to use it if you want.

I'm adding other classes we've found uses for, including a disk-backed LRU cache, a "Pair" object, a low memory release pool, etc.

Enjoy github - Zoosk/ZSFoundation

DougW
  • 28,776
  • 18
  • 79
  • 107
  • this sounds like community wiki to me :) – Blitz Jan 13 '11 at 19:54
  • 2
    If you'd like numbers to be first class citizens in Objective-C, file a feature request at http://bugreport.apple.com. It will be marked as a duplicate, but the more dupes, the better (since it keeps reminding the language developers that people have a need for certain features). – Dave DeLong Jan 13 '11 at 19:57
  • @DougW, are you planning on adding to ZSFloat `compare: withPrecision`? – Stephen Furlani Feb 23 '11 at 21:23
  • @Stephen - Sure, not a bad idea--added it to tracked issues. I was really borderline writing a float class at all. If I'm going to do it though, I suppose I ought to steer people away from anti-patterns. – DougW Feb 23 '11 at 23:06
  • @DougW, Is there a standard? An ISO convention? You could make it a class property, or a compile define or something. – Stephen Furlani Feb 24 '11 at 12:56
  • @Stephen - The short answer? No. The long answer? Absolute error is completely dependent on the numbers used. Here are a few links to relevant material on the subject - (http://hal.archives-ouvertes.fr/hal-00128124/en/) - (http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm) - (http://en.wikipedia.org/wiki/Machine_epsilon) – DougW Feb 24 '11 at 16:53

4 Answers4

5

The most common convention for representing the idea of nil as an NSInteger is to use the NSNotFound value. This is, in fact, equal to NSIntegerMax, though it tends to be more obvious to the reader that this is a sentinel value representing the lack of a number. There are many cases where this is used throughout Cocoa. One common case is as the location field of an NSRange as a return value from -rangeOfString: et al.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • Hey, yeah I agree, but at the end of the day this is a convention and not necessarily safe. We've experienced a number of bugs where a user's height might accidentally be displayed as NSIntegerMax, for example, because the programmer writing the display layer logic wasn't aware of the convention. – DougW Jan 13 '11 at 22:02
  • Sounds like you need to educate your programmers, then. One should never make blind assumptions about the values that are being used. – Lily Ballard Jan 13 '11 at 23:48
  • Heh, okay. It's not a matter of education, it's a matter of dissemination of knowledge. Some NSIntegers may use this convention and some may not. And what about CGFloats? If nobody posts anything better soon I'll go ahead and mark this the answer though, since there may be no better solution. – DougW Jan 18 '11 at 00:02
  • If your programmers aren't aware that there is a sentinel value at all, then the particular value you use won't make much difference. Cocoa almost exclusively uses NSNotFound as its NSInteger/NSUInteger sentinel value. As for CGFloat, sentinel values aren't so common there, but you could use NaN, though if you do then you should be very careful about how you handle it. For example, in iOS 4.2, setting a CALayer center/bounds component to NaN triggers an exception. – Lily Ballard Jan 18 '11 at 01:33
  • Exactly my point. With nil, nobody needs to "be aware" of anything--it's dictated by the architecture, not convention. If I gave you a big library of my code, how would you know what convention I used? Are you going to look up my documentation for every property and trust that I documented correctly everywhere? I'll go ahead and accept this since there isn't a better native solution, but imo this pattern is a disaster in a language founded around fail-safe message passing and nil values. – DougW Feb 09 '11 at 07:52
  • This answer oversimplifies the subtleties of the question by focusing on NSInteger conventions alone. However, Kevin's later comments do hint at other methods of providing handling for other types. In any object-oriented language (Ruby comes to mind in particular) abstracted number classes are a thorny compromise. – ctpenrose Apr 07 '14 at 23:31
4

You could try this?

#define numberWithFloat(float f) \
  [NSNumber numberWithFloat:f]
#define floatFromNumber(NSNumber *n) \
  [n floatValue]

(see my original answer below)

Here's the other thing with NSNumber, you don't have to retrieve what you set.

For example

NSNumber *myInt = [NSNumber numberWithInteger:100];
float myFloat = [myInt floatValue];

is perfectly valid. NSNumber's strength is that it allows you to "weak-type" your primitives, use compare:, use isEqualTo:, and stringValue for easy display.


[EDIT]

User @Dave DeLong says that sub-classing NSNumber is a Bad Idea without much work. Since it's a class cluster (meaning NSNumber is an abstract superclass of a lot of subclasses) you'll have to declare your own storage if you sub-class it. Not recommended, and thanks to Dave for pointing that out.

Stephen Furlani
  • 6,794
  • 4
  • 31
  • 60
  • This is a bad idea without much more work. `NSNumber` is part of a class cluster, and [the documentation has info on what more you need to do](http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/Reference/Reference.html#//apple_ref/doc/uid/20000178-2025) – Dave DeLong Jan 13 '11 at 19:34
  • @Dave, yea, I just checked that. I have no idea what it means, though. Is there something on Class Clusters? – Stephen Furlani Jan 13 '11 at 19:36
  • 2
    @Stephen [The documentation definition of a Class Cluster](http://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ClassCluster.html) – Dave DeLong Jan 13 '11 at 19:39
  • @Stephen - I'm not sure the macro definitions really gain anything over just using NSNumbers in this case. There's still the ambiguity around what type of number a given variable is supposed to represent. And yes, I would not try to subclass NSNumber. I would probably rather create my own classes (eg ZSInteger) that were siblings or children of NSValue, not NSNumber. – DougW Jan 13 '11 at 22:11
  • @Dave - Hey Dave, what about subclassing NSNumber and overriding nothing at all. That would at least give me the ability to disambiguate the intended contents of the object, without mucking with the class cluster. – DougW Jan 13 '11 at 22:30
  • @DougW that won't work. Any time you create a subclass in a class cluster, you must override certain methods for the thing to jive together as a whole. The `NSNumber` docs are very specific about that (especially considering that `NSNumber` is a subclass of `NSValue`) – Dave DeLong Jan 13 '11 at 22:34
  • @Dave - Yeah I was just wondering if it was something intrinsic to the class type itself, or just meant that if you're going to mess with anything you have to mess with everything. In this case I would mess with nothing at all. Think I might just write my own class cluster anyway though. – DougW Jan 13 '11 at 23:13
  • @DougW, you really can't deal with having your primitives be weak-typed? You don't need to know what you stored a number as to retrieve it... – Stephen Furlani Jan 14 '11 at 13:07
  • @Stephen - I mean, is weak typing the end of the world? No, I can certainly work with it. But for example, if you have an NSNumber you need to compare against an NSUInteger, how do you know what's in there? Is it unsigned? Is this a safe thing to do? You'd have to track things back to the source and hope everyone along the way made safe assumptions. I'd rather be explicit, not for my sake but for the sake of collaboration and clarity. – DougW Jan 14 '11 at 22:25
3

As an alternative, there is also the NSDecimal struct for representing numerical types. NSDecimal (and its Objective-C class version NSDecimalNumber) lets you represent true decimals and avoid floating point errors, so it tends to be recommended for dealing with things like currency.

The NSDecimal struct can represent numbers as well as a Not a Number state (a potential nil replacement). You can query whether an NSDecimal is not a number using NSDecimalIsNotANumber(), and generate the Not a Number value with the help of an NSDecimalNumber.

NSDecimals are faster to work with than NSDecimalNumbers, and the structs don't bring the same kind of memory management issues that objects do.

However, there isn't an easy way to get values into NSDecimal form without using a temporary NSDecimalNumber. Also, many of the math functions (like trigonometry operations) that are available for simple floating point numbers aren't yet available for NSDecimal. I'd like to write my own functions that add some of these capabilities, but they would require accessing the internal fields of the struct. Apple labels these as private (with the use of an underscore), but they are present in the headers and I'd guess they're unlikely to change in the future.

Update: Dave DeLong wrote a series of functions for performing NSDecimal trigonometry, roots, and other operations. I've tweaked these to provide up to 34 digits of decimal precision, and the code for these functions can be downloaded here. As a warning, this precision comes at a price in terms of performance, but these functions should be fine for fairly infrequent calculations.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • If you do write your own trig functions for `NSDecimal`, I'd love to know about it! – Dave DeLong Jan 13 '11 at 21:44
  • Interesting, I haven't used NSDecimal before. Not sure it solves my problem, but thanks for pointing it out. – DougW Jan 13 '11 at 22:04
  • NSDecimalNumber is a subclass of NSNumber. – JeremyP Jan 14 '11 at 09:18
  • 1
    @JeremyP - Correct, but I wanted to point out the NSDecimal struct, which is another non-object numerical type. Admittedly, you have to touch NSDecimalNumber at some point when dealing with it, but NSDecimal can have advantages in certain situations. – Brad Larson Jan 14 '11 at 15:08
0

I don't understand why, if you store stuff as an NSNumber, you need to care what type it is. NSNumber will choose an appropriate internal representation and then do any necessary conversion depending on what format you ask for its value in.

If you can't tell what type is appropriate from the name of the variable you need to choose better names. e.g.

numberOfChildren = [NSNumber numberWithFloat: 2.5];

is plainly silly.

And for currency values, I definitely recommend using NSDecimalNumber (which is a subclass of NSNumber) but defines base 10 arithmetic operations so you don't have to worry about float rounding.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Thanks for the answer Jeremy. I think if you look up the controversy around hungarian notation, you'll find a lot of arguments against using a naming convention that's explicit about the variable's type. Sure, numberOfChildren is obvious, but how about "heightInInches"? Is that an integer or does it allow for fractional inches? There are a variety of cases where the the allowable value range isn't clear, even with good naming. The reason I care is because I want to be explicit about things like signed vs unsigned and int vs float across the stack. – DougW Jan 14 '11 at 22:32
  • I will add that there are a number of weakly or even untyped languages, and that's a perfectly valid approach. C and Obj C however are designed to be strongly typed, and there are a lot of reasons not to break that construct. So I'm not saying you couldn't do it your way, I just prefer to stick in the realm of strong typing. That leads to a whole other debate though, so for my particular question I'd like to stick to strong. – DougW Jan 14 '11 at 22:37