okami
okami

Reputation: 2153

get type of NSNumber

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.

Upvotes: 43

Views: 25197

Answers (12)

kgaidis
kgaidis

Reputation: 15579

Swift Version

NSNumber is a class-cluster so each underlying type can be figured from the instance. This code avoids hard-coding the different NSNumber types by creating an instance of the expected type, and then comparing it against the unknown type.

extension NSNumber {
    var isBool: Bool {
        return type(of: self) == type(of: NSNumber(booleanLiteral: true))
    }
}

Upvotes: -1

mickeymoon
mickeymoon

Reputation: 4977

objCType documentation states that The returned type does not necessarily match the method the number object was created with

Secondly, other methods of comparing the class of number to a given class type or assuming boolean number instances to be shared singletons are not documented behaviour.

A more(not completely though) reliable way is to depend on NSJSONSerialisation as it correctly recognises number instances created with bool and outputs true/false in json. This is something we can expect Apple to take care of while moving with new SDKs and on different architectures. Below is the code:

+(BOOL) isBoolType:(NSNumber*) number {

    NSError* err;
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@{@"key":number}
                                                       options:0
                                                         error:&err];

    NSString* jsonString = [[NSString alloc] 
                              initWithData:jsonData
                                  encoding:NSUTF8StringEncoding];

    return [jsonString containsString:@"true"]
            || [jsonString containsString:@"false"];

}

Upvotes: -1

ChikabuZ
ChikabuZ

Reputation: 10185

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
}

Upvotes: 7

Hamidreza Vakilian
Hamidreza Vakilian

Reputation: 860

To check that NSNumber contains a bool value Try this:

if (strcmp([myNumber objCType], [@(YES) objCType]) == 0)
NSLog(@"%@", [myNumber boolValue] ? @"true" : @"false");

Upvotes: 0

cncool
cncool

Reputation: 1026

Use the method -[NSNumber objCType] method to get the type.

If the type's equal to @encode(BOOL), or the number itself is kCFBooleanFalse, or kCFBooleanTrue, it's a boolean.

If it's anything else but 'c', it's a number.

If it's 'c', what appears to be the only way supported way, without checking against private class names, or comparing against undocumented singletons, is to turn make an array of one element, the number, and then use NSJSONSerialization to get the string representation. Finally, check if the string representation contains the string "true" or "false". Here is the full code for checking if an NSNumber is a BOOL:

-(BOOL)isBool
{
    if(!strcmp(self.objCType, @encode(BOOL)) ||
        self == (void*)kCFBooleanFalse ||
        self == (void*)kCFBooleanTrue)
    {
        return YES;
    }

    if(strcmp(self.objCType, "c"))
    {
        return NO;
    }

    NSString * asString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:@[self] options:kNilOptions error:nil] encoding:NSUTF8StringEncoding];

    return [asString containsString:@"true"] || [asString containsString:@"false"];
}

Note that using NSJSONSerialization is slow and if @NO/@YES ever stops always equalling kCFBooleanFalse/kCFBooleanTrue, then this method probably shouldn't be used in a tight loop.

Upvotes: 5

torap
torap

Reputation: 676

check object is of NSNumber type :

if([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]) { //NSNumber }

Upvotes: -2

Jakob Egger
Jakob Egger

Reputation: 12031

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
}

Upvotes: 30

Chris
Chris

Reputation: 40623

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;

Upvotes: 51

user303014
user303014

Reputation: 99

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]) {
  /* ... */
}

Upvotes: 9

Quinn Taylor
Quinn Taylor

Reputation: 44769

The reason the compiler warns you and it doesn't work is because -[NSObject className] is declared in a category on NSObject on Mac OS X (in NSScriptClassDescription.h) and not declared on iPhone. (It doesn't support AppleScript, obviously.) NSStringFromClass([myNum class]) is what you should use to be safe across all platforms. Odds are that -className is declared as a simple wrapper around NSStringFromClass() anyway...

Upvotes: 4

Dave DeLong
Dave DeLong

Reputation: 243146

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.

Upvotes: 75

Alex Wayne
Alex Wayne

Reputation: 187004

NSString *classString = NSStringFromClass([myNum class]);

That should ger the string you want.

Upvotes: 3

Related Questions