API Smell: NSNumber
NSNumber is an Objective-C object wrapper for C and Objective-C’s scalar values, such as BOOL, float, NSInteger, et cetera. It’s commonly used when one needs to store a scalar in an Objective-C collection: NSNumber can be used to “box up” the value and store it in an NSArray or NSDictionary. Most Objective-C developers see NSNumbers when using NSJSONSerialization, which uses NSNumbers to store numeric values in the data structures it’s decoded from (or is encoding to) JSON.
NSNumber should only be used in that capacity: as intermediate storage for a scalar while it’s in an Objective-C collection. It shouldn’t be passed as a method parameter, be assigned or returned by a property on a model object, or be used as a key in an NSDictionary.
NSNumber’s weakness lies in the ambiguous type of its value. If you receive NSNumber *aNumber as a method parameter and want to know its scalar value, what type should it be evaluated as? Is it correct to treat it as a YES/NO, and send it [aNumber boolValue]? Or perhaps it’s a floating-point value and should be sent [aNumber floatValue]? Without introspecting the Objective-C type encoding of an NSNumber, a glance at its value doesn’t hint at the original value. Consider an NSNumber whose integerValue is 1. Is it a signed or unsigned integer? Is it a boolean? Or is it a float that happens to be 1.0?
(Lucas Newman pointed out that it’s technically possible to call objCType on an NSNumber instance to retrieve the type encoding of its original value. However, this should be considered a technique of very last resort.)
If you’re passing an NSNumber to a method, pass it as the appropriate scalar value rather than relying on the recipient knowing (or guessing) the correct underlying type.
If you’re using an NSNumber as a dictionary key, convert it to an NSString. Keys don’t normally represent any particular numeric value: if they do, an NSArray may be a better type of storage.
JSON and Model Objects
When an NSNumber is returned as part of a JSON response, it should be converted to a more appropriate type as soon as possible. An evaluation of the value’s final use will help. If the JSON represents that field as a number (as opposed to a string that happens to contain a numeric value), evaluate how it’s used by the client. If the client makes decisions based on its numeric value, consider declaring an enumeration of valid values:
typedef NS_ENUM(NSInteger, MPObjectType) {
kMPObjectTypeInvalid = 0,
kMPObjectTypeUser,
kMPObjectTypeGroup
};
Once the valid values have been enumerated, a model object created from a JSON response should store its value as a scalar property:
@property (nonatomic, assign) MPObjectType objectType
instead of an NSNumber:
@property (nonatomic, strong) NSNumber *objectType.
Model objects can declare a public property like the one above, and use the NSNumber instance internally in dynamic getter and setter implementations. Here’s an example model object with a BOOL property that’s backed by an NSNumber.
JSON responses may contain a literal number for an object’s ID field. As a rule of thumb, consider this a bug. This integer value typically originates from a relational database being used for storage by the server, and a naïve JSON encoding by the server passes that value along as a numeric field rather than a string. Its type should nonetheless be treated an implementation detail of the database. A client should represent this ID as an NSString and should make no attempt to inspect its numeric value. This ID string will function correctly as a key in an NSDictionary to store and retrieve an object, and will return the correct result when compared to another object’s ID with isEqual:. By clearly indicating the ID as an opaque value with no intrinsic numeric meaning, it helps avoid unwise or untenable uses of the ID, such as sorting objects by the numeric value of their IDs when they need to be ordered. (There’s usually a more appropriate sort; in this case, IDs typically increase over time, so they should be sorted by the objects’ creation dates instead.)
An objective evaluation of one’s use of NSNumber makes for clearer and more future-proof code. NSNumber should be used only as a temporary representation of a numeric value. If an instance of NSNumber has numeric meaning, it should be converted to a scalar value; otherwise, it should be converted to an NSString.