get type of NSNumber

24,092

Solution 1

I recommend using the -[NSNumber objCType] method.

It allows you to do:

NSNumber * n = [NSNumber numberWithBool:YES];
if (strcmp([n objCType], @encode(BOOL)) == 0) {
    NSLog(@"this is a bool");
} else if (strcmp([n objCType], @encode(int)) == 0) {
    NSLog(@"this is an int");
}

For more information on type encodings, check out the Objective-C Runtime Reference.

Solution 2

You can get the type this way, no string comparisons needed:

CFNumberType numberType = CFNumberGetType((CFNumberRef)someNSNumber);

numberType will then be one of:

enum CFNumberType {
   kCFNumberSInt8Type = 1,
   kCFNumberSInt16Type = 2,
   kCFNumberSInt32Type = 3,
   kCFNumberSInt64Type = 4,
   kCFNumberFloat32Type = 5,
   kCFNumberFloat64Type = 6,
   kCFNumberCharType = 7,
   kCFNumberShortType = 8,
   kCFNumberIntType = 9,
   kCFNumberLongType = 10,
   kCFNumberLongLongType = 11,
   kCFNumberFloatType = 12,
   kCFNumberDoubleType = 13,
   kCFNumberCFIndexType = 14,
   kCFNumberNSIntegerType = 15,
   kCFNumberCGFloatType = 16,
   kCFNumberMaxType = 16
};
typedef enum CFNumberType CFNumberType;

Solution 3

If all you want is to differentiate between booleans and anything else, you can make use of the fact that boolean NSNumbers always return a shared instance:

NSNumber *num = ...;
if (num == (void*)kCFBooleanFalse || num == (void*)kCFBooleanTrue) {
   // num is boolean
} else {
   // num is not boolean
}

Solution 4

NSNumber explicitly doesn't guarantee that the returned type will match the method used to create it, so doing this at all is probably a bad idea.

However, you could probably do something like this (you could also compare to objc_getClass("NSCFNumber") etc., but this is arguably more portable):

Class boolClass = [[NSNumber numberWithBool:YES] class];
/* ... */
if([myNum isKindOfClass:boolClass]) {
  /* ... */
}

Solution 5

In Swift:

let numberType = CFNumberGetType(answer)

switch numberType {
case .charType:
    //Bool
case .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .shortType, .intType, .longType, .longLongType, .cfIndexType, .nsIntegerType:
    //Int
case .float32Type, .float64Type, .floatType, .doubleType, .cgFloatType:
    //Double
}
Share:
24,092
okami
Author by

okami

Updated on July 09, 2022

Comments

  • okami
    okami almost 2 years

    I want to get the type of NSNumber instance.

    I found out on http://www.cocoadev.com/index.pl?NSNumber this:

     NSNumber *myNum = [[NSNumber alloc] initWithBool:TRUE];
    
     if ([[myNum className] isEqualToString:@"NSCFNumber"]) {
      // process NSNumber as integer
     } else if  ([[myNum className] isEqualToString:@"NSCFBoolean"]) {
      // process NSNumber as boolean
     }
    

    Ok, but this doesn't work, the [myNum className] isn't recognized by the compiler. I'm compiling for iPhone.

  • okami
    okami about 14 years
    isn't there a way to get the type of the NSNumber without comparing with strings? I know that there is the "objCType" method, but each time I retrieve it, it returns a different value.
  • jpswain
    jpswain over 12 years
    +1 cool tip! Strange that they don't have a method to get this out of NSNumber much easier.
  • th_in_gs
    th_in_gs almost 12 years
    This doesn't work - at least on iOS: (lldb) p (char *)[[NSNumber numberWithBool:YES] objCType] - it encodes the bool as a char internally (which is correct at the machine level, but not at the intentional level)
  • Michael Manner
    Michael Manner almost 12 years
    Unfortunately, there is no kCFNumberBoolType to distinguish boolean values from characters, so this does not work for all cases.
  • kylef
    kylef almost 10 years
    This information is dated, it won't work on 64-bit iOS devices and simulators and therefore it should not be used. It can lead to very hard to find issues which will only occur on 64-bit iOS devices.
  • Ethan Holshouser
    Ethan Holshouser almost 10 years
    @kylef In what way does it fail on 64-bit devices?
  • bluevoid
    bluevoid over 9 years
    Both kCFNumberCharType == CFNumberGetType((__bridge CFNumberRef)nsValue) and 0 == strcmp([nsValue objCType], "c") work on 32 and 64bit systems, but if you're unfortunate enough to support code that cares, I think the former "feels" marginally safer.
  • zneak
    zneak over 9 years
    The documentation notes a very important special consideration: "The returned type does not necessarily match the method the number object was created with."
  • Rick77
    Rick77 about 9 years
    +1 for the ingenuity, but I wouldn't recommend it for production code (the solution is waaaay too fragile...)
  • Jakob Egger
    Jakob Egger about 9 years
    It depends what the failure mode is. I use it to display "TRUE" or "FALSE" for Booleans; in the unlikely case Apple changes this implementation detail, my app would display "1" or "0" instead; I can live with that. (Especially since there is no alternative besides writing your own NSNumber subclass that keeps track of the type it was created with)
  • Rick77
    Rick77 about 9 years
    Granted: your solution is the best so far and, as you correctly point out, the only one (about the NSNumber subclass: I came here because I wanted to distinguish booleans vs integers in a plist, much good it would do to me...). Also granted that in case of failure, the solution would return correct truthy and falsy values. Point is, either you care whether a value is a boolean (and you can't accept false negatives), or you don't (so why bother? :) )
  • malhal
    malhal almost 9 years
    if(num == [NSNumbler numberWithBool:YES] || ... also works
  • Jakob Egger
    Jakob Egger almost 9 years
    You could even use if (num==@(YES) || num==@(NO)). But using Core Foundation constants looks really sophisticated, while comparing Objective C objects with == looks like a newbie mistake ;)
  • Greg Brown
    Greg Brown over 8 years
    @EthanHolshouser On 32-bit systems, both [n objCType] and @encode(BOOL) return "c", but on 64-bit systems, @encode(BOOL) returns "B". This seems like a bug in NSNumber to me.
  • Greg Brown
    Greg Brown over 8 years
    @JakobEgger Where is it documented that NSNumber always returns a shared instance for boolean values? I haven't been able to verify that. However, [num isEqual:@YES] || [num isEqual:@NO] should work in either case.
  • Jakob Egger
    Jakob Egger over 8 years
    @GregBrown To my knowledge it is not officially documented. But it is officially documented that NSNumber is toll-free bridged to CFNumberRef, and if you look at CFNumber.c it's obvious that all the CFBoolean* functions just compare against a static pointer (kCFBooleanTrue). Therefore NSNumber must return the shared instance, otherwise it would not be compatible with CFBoolean.
  • Jakob Egger
    Jakob Egger over 8 years
    @GregBrown [num isEqual:@YES] will not work, because [@1 isEqual:@YES] will return true!
  • Greg Brown
    Greg Brown over 8 years
    @JakobEgger I just discovered that. It's annoying. A number should never be logically equal to a boolean. In any case, I'm reluctant to rely on an undocumented solution. In fact, I tried your approach earlier today and Xcode generates this warning: "warning: direct comparison of a numeric literal has undefined behavior".
  • Jakob Egger
    Jakob Egger over 8 years
    The original num == (void*)kCFBooleanFalse code does not generate a warning. If you need a distinct Boolean type, and don't want to rely on an implementation detail, I recommend creating a custom class to represent boolean values.
  • Greg Brown
    Greg Brown over 8 years
    @JakobEgger A custom class won't work for me - I need to support native Swift Bool types. The original solution may be OK for my needs, but your second suggestion does generate a warning.
  • andreacipriani
    andreacipriani about 8 years
    In my tests this method fails on iPad with iOS 8.4
  • Dmitry Makarenko
    Dmitry Makarenko over 7 years
    It's because of how the BOOL type is defined: #if (TARGET_OS_IPHONE && __LP64__) || TARGET_OS_WATCH #define OBJC_BOOL_IS_BOOL 1 typedef bool BOOL; #else #define OBJC_BOOL_IS_CHAR 1 typedef signed char BOOL; // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" // even if -funsigned-char is used. #endif
  • hariszaman
    hariszaman over 7 years
    this is bad it is not a boolClass but numberClass with bool value
  • Cœur
    Cœur over 6 years
    Could be correct, but please note that Swift can distinguish between Float32 (aka Float), Float64 (aka Double) and Float80, so maybe, just maybe, in case of .float32Type and .floatType, it is a Float instead. We would need a demonstration or a reference to verify.
  • Cœur
    Cœur over 5 years
    Well, seeing that there are only three literal initializers for NSNumber (Int, Double, Bool), I guess that this answer is as accurate as Apple Swift developers want it to be.
  • Cœur
    Cœur over 5 years
    This will only work to distinguish boolean from non-boolean. For instance, an NSNumber initialized from an integerLiteral and from a floatLiteral will share the same class-cluster, so your NSNumber(floatLiteral: 0.1).isInt is true. And there is a well-known NSNumber subclass that will not compare well: NSDecimalNumber(integerLiteral: 0).isInt is false.
  • Cœur
    Cœur over 5 years
    Having the overhead of a JSON tokenizer and a long arbitrary String is not a way I would recommend to deal with Boolean detection. Jakob Egger answer is more elegant and is backed up by documentation that NSNumber is toll-free bridged to CFNumberRef, itself directly comparable to two static values.
  • Cœur
    Cœur over 5 years
    Apple and people's frameworks are free to create additional subclasses of NSNumber supporting a boolean, so this solution shouldn't claim it's future-proof.
  • Cœur
    Cœur over 5 years
    @MichaelManner you should be using Unichar and/or NSValue for storing characters, not NSNumber.
  • kgaidis
    kgaidis over 5 years
    @Cœur thank you for the note, what would you suggest as the best way to do this?
  • Cœur
    Cœur over 5 years
    ChikabuZ answer (with CFNumberGetType) is currently my favourite: most accurate info we can get in the general case.