get type of NSNumber - objective-c

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.

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.

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;

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
}

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

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
}

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.

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...

NSString *classString = NSStringFromClass([myNum class]);
That should ger the string you want.

To check that NSNumber contains a bool value Try this:
if (strcmp([myNumber objCType], [#(YES) objCType]) == 0)
NSLog(#"%#", [myNumber boolValue] ? #"true" : #"false");

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"];
}

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))
}
}

check object is of NSNumber type :
if([obj isKindOfClass:NSClassFromString(#"__NSCFNumber")])
{
//NSNumber
}

Related

how to test object equality using xcttest?

My method returns a NSNumber* and I want to unit test this method. Since my actual return value is NSNumber*, I create a new expected value of NSNumber*, but it fails. Here is the code:
NSNumber *cRating = [movie getRating:ratingDictionary ratingType:criticRating];
XCTAssertEqualObjects(cRating, [[NSNumber alloc]initWithInt:70], #"");
The error is:
[SFModelTest testGetCriticRatingMethod] failed: ((cRating) equal to ([[NSNumber alloc]initWithInt:70])) failed: ("70") is not equal to ("70")
Since it is saying "70" is not equal to "70", I am guessing it has to do with alloc init. Some pointer stuff that is not equal. Can somebody please help? Thank you.
Edit for comment: adding getRating method
- (NSNumber *)getRating:(NSDictionary *)movieDic ratingType:(enum RatingsEnum) rating{
NSNumber *result = 0;
NSNumber *ratingNum = 0;
switch (rating) {
case userRating:
{
ratingNum = [movieDic objectForKey:#"audience_score"];
break;
}
case criticRating:
{
ratingNum = [movieDic objectForKey:#"critics_score"];
break;
}
default:
break;
}
if(ratingNum && ratingNum > 0)
{
result = ratingNum;
}
return result;
}
The method returns NSNumber. And my test is:
NSNumber *cRating = [movie getRating:ratingDictionary ratingType:criticRating];
XCTAssertEqualObjects(cRating, [[NSNumber alloc]initWithInt:70], #"");
When I do a class NSLOG, it returns __NSCFConstantString. I am confused now.
I think that you're doing something wrong because XCTAssertEqualObjects(#(1), #(1), #"Not equal."); or XCTAssertEqualObjects([NSNumber numberWithInt:1], [NSNumber numberWithInt:1], #"Not equal."); if you are not familiar with literals, will pass.
You should checkout the getRating:ratingType: method to see which type of object it returns.
Try adding a breakpoint just before that XCTAssertEqualObjects and inspect the cRating instance.

NSFormatter for BOOL

I have set up my simple Xcode project with a table that is binded to an array controller. It works fine if the array controller is full of entities with a string attribute. However I want to change the attribute to a BOOL and have the table show the string "true" or "false" based on the BOOL.
I have overrided the following two methods from NSFormatter:
-(NSString*) stringForObjectValue:(id)object {
//what is the object?
NSLog(#"object is: %#", object);
if(![object isKindOfClass: [ NSString class ] ] ) {
return nil;
}
//i'm tired....just output hello in the table!!
NSString *returnStr = [[NSString alloc] initWithFormat:#"hello"];
return returnStr;
}
-(BOOL)getObjectValue: (id*)object forString:string errorDescription:(NSString**)error {
if( object ) {
return YES;
}
return NO;
}
So the table gets populated with "hello" if the attribute is a string however if I switch it to a boolean, then the table gets populated with lots of blank spaces.
I don't know if this helps but on the line where I'm outputting the object, it outputs __NSCFString if the attribute is a string and "Text Cell" if I switch the attribute to a boolean. This is something else I don't understand.
Ok, it's not 100% clear what you're trying to do from the code, but first things first - BOOL is not an object, it's basically 0 or 1, so to place BOOL values into an array, you're probably best off using NSNumber:
NSNumber *boolValue = [NSNumber numberWithBool:YES];
and placing these into your array. Now you want to change your method:
-(NSString*) stringForObjectValue:(id)object {
NSNumber *number = (NSNumber *)object;
if ([number boolValue] == YES)
return #"true";
else
return #"false";
}
There's a few things here - for example, you want to avoid passing around id references if you can (if you know all your objects in the NSArray are NSNumber, you shouldn't need to).

iOS asserting NSMutableArray contains 2 objects, regardless of index

I have the following test case in my iOS application :
-(void) testTwoDefaultUsersExist
{
NSString * expected;
NSString * actual;
expected = #"John Smith";
actual = [[[userService getAllUsers]objectAtIndex:0] fullName];
STAssertEqualObjects(expected, actual, #"Not equal");
expected = #"Dave Brown";
actual = [[[userService getAllUsers]objectAtIndex:1] fullName];
STAssertEqualObjects(expected, actual, #"Not equal");
}
The above just checks that my call to [userService getAllUsers] returns 2 User objects, one with a name of John Smith, the other with Dave Brown. This appears to work fine for this scenario, but I have other cases where that ordering may change, so John may be placed in index 1 rather than 0
Question : How can I assert that the NSMutableArray, being returned from the call to [userService getAllUsers] contains those 2 objects, regardless of ordering?
Can you not simply use the NSArray method -containsObject:? An NSMutableArray is still an NSArray, so you can do:
NSArray * expected = [NSArray arrayWithObjects:#"John Smith", #"Dave Brown", nil];
NSArray * actual = [[userService getAllUsers] valueForKey:#"fullName"];
for(NSString * name in expected) {
STAssertTrue([actual containsObject:name], #"Missing name");
}
Note the (ab)use of -valueForKey: to transform an array of user objects into an array of NSString objects, making the -containsObject: call simpler. This will only work if your user object is key-value coding compliant for the fullName property.
NSMutableArray always contains the elements as you insert them to the array
You can iterate over the elements that you insert and test if they're at the NSArray using:
- (NSUInteger)indexOfObject:(id)anObject
If the object is not found it returns NSNotFound, that can be used with the Unit Test Framework that you choice
Greetings
Assert on equality like this
NSAssert1([userService getAllUsers].count == 2, #"SomeDescription", nil);
If you want to search an array for the existence of some strings, use the following function
- (BOOL) containsAllNames:(NSArray*)arrToSearch namesToSearch:(NSArray*)arr
{
BOOL containsAll = YES;
for (NSString *name in arr) {
BOOL containsCurrent = NO;
for (NSString *nameToSearch in arrToSearch) {
if ([name isEqualToString:nameToSearch]) {
containsCurrent = YES;
break;
}
}
if (!containsCurrent) {
containsAll = NO;
}
}
return containsAll;
}
Call it like
NSArray *toSearch = [NSArray arrayWithObjects:#"John Smith", #"Dave Brown", nil];
[self containsAllNames:YourArray namesToSearch:toSearch];

Objective-C:How to Converting int to id for selector's object?

I received a return value from SQLite fetch
int primaryKey = sqlite3_column_int(statement, 0);
and I'm going to use it as a selector's object:
[[ABC alloc] performSelector:#selector(abcWithAAA:) withObject:[NSNumber numberWithInt:primaryKey]];
the NSLog result for primarykey is a number 4:
NSLog(#"primaryKey:%i",primaryKey);
4
but the NSLog result for [NSNumber numberWithInt:primaryKey] is 131628896.
why? and how do i convert the int value correctly?
Thanks!
I solved the problem using an adapter method that does the cast for the withObject method.
My problem was that I wanted to use a typedef enum and pass it as value to the withObject.
I wanted to call this method using the performSelect message:
-(void) requestInfosAndPersistByMonsterType:(MonsterTypes)monsterType {
}
As you see it request a MonsterTypes typedef defined like this:
typedef enum
{
MonsterTypeIWerwolf = 0,
MonsterTypeITempler = 1,
MonsterTypeIUndefined,
} MonsterTypes;
Actually to be able to call the method above I build this adapter that calls it then:
-(void)monsterTypeFromObject:(id)_monsterType {
if ([_monsterType respondsToSelector:#selector(intValue)]) {
int _t = [_monsterType intValue];
switch (_t) {
case MonsterTypeIWerwolf:
_t = MonsterTypeIWerwolf;
break;
case MonsterTypeITempler:
_t = MonsterTypeITempler;
break;
default:
_t = MonsterTypeIUndefined;
break;
}
[self requestInfosAndPersistByMonsterType:_t];
}
}
It is used this way:
[self performSelector:#selector(monsterTypeFromObject:) withObject:[NSNumber numberWithUnsignedInt:monsterType] afterDelay:5.0f];
You can find it explained in more detail here:
http://kerkermeister.net/objective-c-adapter-from-nsinteger-to-id-when-using-performselector-withobject/
When you log [NSNumber numberWithInt:primaryKey], you're logging the address of an NSNumber object. If you want to see what's inside it should be [[NSNumber numberWithInt:primaryKey] intValue].
In other words, there's nothing in that to suggest your conversion is a problem.
[NSNumber numberWithInt:primaryKey] is object.
use %# for objects.
NSLog(#"%#", [NSNumber numberWithInt:primaryKey]);
131628896 is the memory address of the NSNumber object.
use:
- (void)abcWithAAA: (NSNumber *)number {
int primaryKey = [number intValue];
NSLog("%i", primaryKey);
}

How can I add a boolean value to a NSDictionary?

Well, for integers I would use NSNumber. But YES and NO aren't objects, I guess. A.f.a.i.k. I can only add objects to an NSDictionary, right?
I couldn't find any wrapper class for booleans. Is there any?
You use NSNumber.
It has init... and number... methods that take booleans, just as it does integers and so on.
From the NSNumber class reference:
// Creates and returns an NSNumber object containing a
// given value, treating it as a BOOL.
+ (NSNumber *)numberWithBool:(BOOL)value
and:
// Returns an NSNumber object initialized to contain a
// given value, treated as a BOOL.
- (id)initWithBool:(BOOL)value
and:
// Returns the receiver’s value as a BOOL.
- (BOOL)boolValue
The new syntax since Apple LLVM Compiler 4.0
dictionary[#"key1"] = #(boolValue);
dictionary[#"key2"] = #YES;
The syntax converts BOOL to NSNumber, which is acceptable to NSDictionary.
If you are declaring it as a literal and you are using clang v3.1 or above, you should use #NO / #YES if you are declaring it as a literal. E.g.
NSMutableDictionary* foo = [#{ #"key": #NO } mutableCopy];
foo[#"bar"] = #YES;
For more info on that:
http://clang.llvm.org/docs/ObjectiveCLiterals.html
As jcampbell1 pointed out, now you can use literal syntax for NSNumbers:
NSDictionary *data = #{
// when you always pass same value
#"someKey" : #YES
// if you want to pass some boolean variable
#"anotherKey" : #(someVariable)
};
Try this:
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setObject:[NSNumber numberWithBool:TRUE] forKey:#"Pratik"];
[dic setObject:[NSNumber numberWithBool:FALSE] forKey:#"Sachin"];
if ([dic[#"Pratik"] boolValue])
{
NSLog(#"Boolean is TRUE for 'Pratik'");
}
else
{
NSLog(#"Boolean is FALSE for 'Pratik'");
}
if ([dic[#"Sachin"] boolValue])
{
NSLog(#"Boolean is TRUE for 'Sachin'");
}
else
{
NSLog(#"Boolean is FALSE for 'Sachin'");
}
The output will be as following:
Boolean is TRUE for 'Pratik'
Boolean is FALSE for 'Sachin'