get type of NSNumber
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
}
okami
Updated on July 09, 2022Comments
-
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 about 14 yearsisn'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 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 almost 12 yearsThis 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 almost 12 yearsUnfortunately, there is no kCFNumberBoolType to distinguish boolean values from characters, so this does not work for all cases.
-
kylef almost 10 yearsThis 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 almost 10 years@kylef In what way does it fail on 64-bit devices?
-
bluevoid over 9 yearsBoth 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 over 9 yearsThe documentation notes a very important special consideration: "The returned type does not necessarily match the method the number object was created with."
-
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 about 9 yearsIt 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 about 9 yearsGranted: 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 almost 9 yearsif(num == [NSNumbler numberWithBool:YES] || ... also works
-
Jakob Egger almost 9 yearsYou 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 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 inNSNumber
to me. -
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 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 over 8 years@GregBrown [num isEqual:@YES] will not work, because [@1 isEqual:@YES] will return true!
-
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 over 8 yearsThe 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 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 about 8 yearsIn my tests this method fails on iPad with iOS 8.4
-
Dmitry Makarenko over 7 yearsIt'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 over 7 yearsthis is bad it is not a boolClass but numberClass with bool value
-
Cœur over 6 yearsCould be correct, but please note that Swift can distinguish between
Float32
(akaFloat
),Float64
(akaDouble
) andFloat80
, so maybe, just maybe, in case of .float32Type and .floatType, it is aFloat
instead. We would need a demonstration or a reference to verify. -
Cœur over 5 yearsWell, 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 over 5 yearsThis 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
istrue
. And there is a well-known NSNumber subclass that will not compare well:NSDecimalNumber(integerLiteral: 0).isInt
isfalse
. -
Cœur over 5 yearsHaving 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 over 5 yearsApple 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 over 5 years@MichaelManner you should be using Unichar and/or NSValue for storing characters, not NSNumber.
-
kgaidis over 5 years@Cœur thank you for the note, what would you suggest as the best way to do this?
-
Cœur over 5 yearsChikabuZ answer (with
CFNumberGetType
) is currently my favourite: most accurate info we can get in the general case.