How to Properly set an integer value in NSDictionary? - objective-c

The following code snippet:
NSLog(#"userInfo: The timer is %d", timerCounter);
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:timerCounter] forKey:#"timerCounter"];
NSUInteger c = (NSUInteger)[dict objectForKey:#"timerCounter"];
NSLog(#"userInfo: Timer started on %d", c);
produces output along the lines of:
2009-10-22 00:36:55.927 TimerHacking[2457:20b] userInfo: The timer is 1
2009-10-22 00:36:55.928 TimerHacking[2457:20b] userInfo: Timer started on 5295968
(FWIW, timerCounter is a NSUInteger.)
I'm sure I'm missing something fairly obvious, just not sure what it is.

You should use intValue from the received object (an NSNumber), and not use a cast:
NSUInteger c = [[dict objectForKey:#"timerCounter"] intValue];

Dictionaries always store objects. NSInteger and NSUInteger are not objects. Your dictionary is storing an NSNumber (remember that [NSNumber numberWithInteger:timerCounter]?), which is an object. So as epatel said, you need to ask the NSNumber for its unsignedIntegerValue if you want an NSUInteger.

Or like this with literals:
NSUInteger c = ((NSNumber *)dict[#"timerCounter"]).unsignedIntegerValue;
You must cast as NSNumber first as object pulled from dictionary will be id_nullable and so won't respond to the value converting methods.

Related

NSNumber returns wrong value

I have NSDictionary and I assign value of 'price' key to NSNumber property of my object but when I log number value, it is an strange negative number!
item.price = [food valueForKey:#"price"];
price property is NSNumber
[[food valueForKey:#"price"] class]
will print __NSCFNumber which is right.
[[food valueForKey:#"price"] intValue]
returns 0x000000000000c350 which is 50000 thats right
but when I log item.price or [item.price integerValue] it is -15536 which is completely wrong!
Any idea?
food json sample:
{
"title":"Pizza",
"price":50000
}
Guess what?
all of this logs and problems shows my NSNumber property can't store some integers. but what range of integers? integers which are between -32,768 / 32,768
but why? because I have define my managed object price property of kind NSInteger 16!
I should define it as NSInteger 32
Let's say you are trying to do this:
NSDictionary *inventory = #{
#"price" : [NSNumber numberWithInt:50000]
};
NSNumber *num= [inventory valueForKey:#"price"];
NSLog(#"%d,%f,%ld,%#",num,num,num,num);
and the log will display as:
800002,-0.000000,-5764607523033434878,50000
all of them are totally different.The last one is the correct NSNumber object
NSNumber is an object ,so you need to use %# to log it and remaining %d,%f,%ld are not objects.

-[__NSCFNumber addObject:]: unrecognized selector sent to instance [duplicate]

This question already has answers here:
How can I debug 'unrecognized selector sent to instance' error
(9 answers)
Closed 8 years ago.
I created a multidimensional array as follows:
NSMutableArray *subArray = [NSMutableArray arrayWithObjects:[NSString string], [NSNumber numberWithInt:0], [NSMutableArray array], nil];
self.dataArray = [[NSMutableArray alloc] initWithCapacity:9];
for (int i = 0; i < 9; i++) {
[self.dataArray addObject:subArray];
}
then when I try to access and change values like this
NSNumber *num = self.dataArray[0][1];
int numInt = [num intValue];
NSNumber *newNum = [NSNumber numberWithInt:numInt + 1];
[self.dataArray[0][1] addObject:newNum];
// add item to dataArray
NSMutableArray *tmpArr= self.dataArray[0][2];
[tmpArr addObject:item];
[self.dataArray[0][2] addObject:tmpArr];
but I'm getting
-[__NSCFNumber addObject:]: unrecognized selector sent to instance
what exactly is the problem, I don't understand, thanks in advance!
In the first line you are treating the object in the array as a NSNumber (which it obviously is):
NSNumber *num = self.dataArray[0][1];
And here you treat the exact same object like an NSMutableArray:
[self.dataArray[0][1] addObject:newNum];
That won't work, because that object is an instance of NSNumber.
I don't know what you achieve so I can't help you with the correct code, but that's where your problem is. Maybe you just wanted to write:
[self.dataArray[0][2] addObject:newNum];
You should probably stop to use an "inner array" as data storage and switch to using a proper subclass. Currently your code is pretty much unreadable, using proper Objects to store your values would improve it a lot.
Btw, your multidimensional array is actually just one dimensional, because you add the exact same array multiple times.
You probably want to do this:
for (int i = 0; i < 9; i++) {
NSMutableArray *subArray = [NSMutableArray arrayWithObjects:[NSString string], [NSNumber numberWithInt:0], [NSMutableArray array], nil];
[self.dataArray addObject:subArray];
}
NSNumber *num = self.dataArray[0][1];
int numInt = [num intValue];
NSNumber *newNum = [NSNumber numberWithInt:numInt + 1];
[self.dataArray[0][1] addObject:newNum]; // 3. also, possibly here
// add item to dataArray
NSMutableArray *tmpArr= self.dataArray[0][2]; // 1. here
[tmpArr addObject:item]; // 2. and here
[self.dataArray[0][2] addObject:tmpArr];
You're getting this error because the first line I marked, tmpArr is actually of type NSNumber. NSNumber is a class cluster, which is why you're seeing __NSCFNumber throw the error. All that is, is just a private subclass of NSNumber.
So the error is being thrown because you're trying to call addObject on a type of object that doesn't support it. Personally I wouldn't store more than one type of object in an array, but I don't know exactly what you're doing. Assuming you don't change the way you're storing things, what you can do is this:
NSMutableArray *tmpArr= self.dataArray[0][2];
if ([tmpArr isKindOfClass:[NSMutableArray class])
{
[tmpArr addObject:item];
}
else
{
NSLog(#"Woops, trying to add an object to something that's not a mutable array");
}
You would have to do this everytime you try to store an object into an array that you're pulling out of self.dataArray. What this does is verify that tmpArr is what you think it is.
Alternatively, you could check if it responds to addObject
if ([tmpArr respondsToSelector:#selector(addObject:)])
{
[tmpArr addObject:item];
}
The second way doesn't care what class it is, only if the method addObject can be used.

discussing functionality of id's stringValue function?

I got a NSMutableArray object with int values
and I can get a certain value via :
int *v0=[[[arrayObj objectAtIndex:0] intValue];
there is no problem.
But
I got a NSMutableArray object with NSString values
and I cannot get a certain value via :
NSString *v0=[[[arrayObj objectAtIndex:0] stringValue];
//raises error
I want to learn and understand exactly what stringValue for... and why this error occurs ?
NSString *v0=[arrayObj objectAtIndex:0];
works as expected.I asusme its some kind of pointer with null terminated so it can leech value.
Im not sure this line is also unicode/encoded string safe code.
in conclusion:
want to know the purpose of stringValue with some lines o code snippets
I got a NSMutableArray object with int values
That's not possible, Cocoa arrays always contain objects. You probably have an array of NSNumber objects that wrap the integers, like:
NSArray *arrayOfNumbers = #[#1, #2, #3];
NSNumber objects have an intValue method, so this works:
int value = [arrayOfNumbers[0] intValue];
On the other hand when you have an array of strings ...
NSArray *arrayOfStrings = #[#"1", #"2", #"3"];
... you want to access individual elements directly, without converting the string object to something else:
NSString *element = arrayOfStrings[0];
NSString objects do not understand the stringValue method:
[arrayOfStrings[0] stringValue]; // crash: does not recognize selector
Back at the beginning, our NSNumber objects from the first array do understand stringValue. You can use it to convert the number to a string:
NSString *intString = [arrayOfNumbers[0] stringValue];
To make the confusion perfect, NSString also understand the intValue message:
int value = [arrayOfStrings[0] intValue];
Here intValue means to try to convert the string to a plain C int value.
The error you will be getting (but failing to post with your question) will be Unknown selector sent to instance and this is because NSString doesn't have a stringValue method.
The approach you suggest is correct:
NSString *v0 = [arrayObj objectAtIndex:0];
EDIT (prompted by #Answerbot's answer):
The reason you are confused is that [NSString intValue] is used to convert the string value to an integer, as long as the string represents an integer (i.e. #"123"). However you don't need this for string as the object is already a string. It's therefore not provided.

Adding negative NSNumber value to a NSDictionary

As someone who has some programming experience it pains me to be asking this question. I just started playing around with objective-c a few days ago and I am trying to simply add NSNumber objects to an NSDictionary. The problem is, when I add an NSNumber object with a negative value it seems as if it is being added as a string not an NSNumber.
Here is how I am initializing the dictionary:
testDict = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithDouble:-3],#"x",
[NSNumber numberWithDouble:7, #"a",
nil];
I guess I really have two questions, 1.) Is this not how you create an NSNumber object that has a negative value?
2.) When I print out the dictionary I get the following:
NSLog(#"dictionary = %#", self.testDict);
a = 7;
x = "-3";
Why the double quotes around the -3?
You're correct, and everything's fine. That's just the dictionary -description being misleading.
To verify, break on the NSLog() and try (warning: typed on iPhone):
p [testDict objectForKey:#"x"];
It should reveal it to be an NSNumber instance.
#Conrad Shultz is right, it's just an artifact of how the the description method for the NSDictionary prints the dictionary contents (which is what is happening when you pass the dictionary to NSLog)
Another way to verify that everything is really working as expected is to iterate through the dictionary members and print the descriptions of the indivdual objects. Then you can see your negative number description looks like a number rather than a string.
NSDictionary* testDict = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithDouble:-3],#"x", [NSNumber numberWithDouble:7], #"a", nil];
NSArray *keys = [testDict allKeys];
for (NSString *key in keys) {
NSLog(#"%# => %#", key, [testDict objectForKey:key]);
}
Console output is:
2012-02-29 12:38:39.544 test10[1055:f803] x => -3
2012-02-29 12:38:39.546 test10[1055:f803] a => 7

Difference between objectForKey and valueForKey?

What is the difference between objectForKey and valueForKey?
I looked both up in the documentation and they seemed the same to me.
objectForKey: is an NSDictionary method. An NSDictionary is a collection class similar to an NSArray, except instead of using indexes, it uses keys to differentiate between items. A key is an arbitrary string you provide. No two objects can have the same key (just as no two objects in an NSArray can have the same index).
valueForKey: is a KVC method. It works with ANY class. valueForKey: allows you to access a property using a string for its name. So for instance, if I have an Account class with a property accountNumber, I can do the following:
NSNumber *anAccountNumber = [NSNumber numberWithInt:12345];
Account *newAccount = [[Account alloc] init];
[newAccount setAccountNumber:anAccountNUmber];
NSNumber *anotherAccountNumber = [newAccount accountNumber];
Using KVC, I can access the property dynamically:
NSNumber *anAccountNumber = [NSNumber numberWithInt:12345];
Account *newAccount = [[Account alloc] init];
[newAccount setValue:anAccountNumber forKey:#"accountNumber"];
NSNumber *anotherAccountNumber = [newAccount valueForKey:#"accountNumber"];
Those are equivalent sets of statements.
I know you're thinking: wow, but sarcastically. KVC doesn't look all that useful. In fact, it looks "wordy". But when you want to change things at runtime, you can do lots of cool things that are much more difficult in other languages (but this is beyond the scope of your question).
If you want to learn more about KVC, there are many tutorials if you Google especially at Scott Stevenson's blog. You can also check out the NSKeyValueCoding Protocol Reference.
When you do valueForKey: you need to give it an NSString, whereas objectForKey: can take any NSObject subclass as a key. This is because for Key-Value Coding, the keys are always strings.
In fact, the documentation states that even when you give valueForKey: an NSString, it will invoke objectForKey: anyway unless the string starts with an #, in which case it invokes [super valueForKey:], which may call valueForUndefinedKey: which may raise an exception.
Here's a great reason to use objectForKey: wherever possible instead of valueForKey: - valueForKey: with an unknown key will throw NSUnknownKeyException saying "this class is not key value coding-compliant for the key ".
As said, the objectForKey: datatype is :(id)aKey whereas the valueForKey: datatype is :(NSString *)key.
For example:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:#"123"],[NSNumber numberWithInteger:5], nil];
NSLog(#"objectForKey : --- %#",[dict objectForKey:[NSNumber numberWithInteger:5]]);
//This will work fine and prints ( 123 )
NSLog(#"valueForKey : --- %#",[dict valueForKey:[NSNumber numberWithInteger:5]]);
//it gives warning "Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'" ---- This will crash on runtime.
So, valueForKey: will take only a string value and is a KVC method, whereas objectForKey: will take any type of object.
The value in objectForKey will be accessed by the same kind of object.
This table represents four differences between objectForKey and valueForKey.
objectForKey
valueForKey
Works on ...
NSDictionary
NSDictionary / KVC
Throws exception
No
Yes (on KVC)
Feed
NSObject's subclass
NSString
Usage on KVC
cannot
can
I'll try to provide a comprehensive answer here. Much of the points appear in other answers, but I found each answer incomplete, and some incorrect.
First and foremost, objectForKey: is an NSDictionary method, while valueForKey: is a KVC protocol method required of any KVC complaint class - including NSDictionary.
Furthermore, as #dreamlax wrote, documentation hints that NSDictionary implements its valueForKey: method USING its objectForKey: implementation. In other words - [NSDictionary valueForKey:] calls on [NSDictionary objectForKey:].
This implies, that valueForKey: can never be faster than objectForKey: (on the same input key) although thorough testing I've done imply about 5% to 15% difference, over billions of random access to a huge NSDictionary. In normal situations - the difference is negligible.
Next: KVC protocol only works with NSString * keys, hence valueForKey: will only accept an NSString * (or subclass) as key, whilst NSDictionary can work with other kinds of objects as keys - so that the "lower level" objectForKey: accepts any copy-able (NSCopying protocol compliant) object as key.
Last, NSDictionary's implementation of valueForKey: deviates from the standard behavior defined in KVC's documentation, and will NOT emit a NSUnknownKeyException for a key it can't find - unless this is a "special" key - one that begins with '#' - which usually means an "aggregation" function key (e.g. #"#sum, #"#avg"). Instead, it will simply return a nil when a key is not found in the NSDictionary - behaving the same as objectForKey:
Following is some test code to demonstrate and prove my notes.
- (void) dictionaryAccess {
NSLog(#"Value for Z:%#", [#{#"X":#(10), #"Y":#(20)} valueForKey:#"Z"]); // prints "Value for Z:(null)"
uint32_t testItemsCount = 1000000;
// create huge dictionary of numbers
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:testItemsCount];
for (long i=0; i<testItemsCount; ++i) {
// make new random key value pair:
NSString *key = [NSString stringWithFormat:#"K_%u",arc4random_uniform(testItemsCount)];
NSNumber *value = #(arc4random_uniform(testItemsCount));
[d setObject:value forKey:key];
}
// create huge set of random keys for testing.
NSMutableArray *keys = [NSMutableArray arrayWithCapacity:testItemsCount];
for (long i=0; i<testItemsCount; ++i) {
NSString *key = [NSString stringWithFormat:#"K_%u",arc4random_uniform(testItemsCount)];
[keys addObject:key];
}
NSDictionary *dict = [d copy];
NSTimeInterval vtotal = 0.0, ototal = 0.0;
NSDate *start;
NSTimeInterval elapsed;
for (int i = 0; i<10; i++) {
start = [NSDate date];
for (NSString *key in keys) {
id value = [dict valueForKey:key];
}
elapsed = [[NSDate date] timeIntervalSinceDate:start];
vtotal+=elapsed;
NSLog (#"reading %lu values off dictionary via valueForKey took: %10.4f seconds", keys.count, elapsed);
start = [NSDate date];
for (NSString *key in keys) {
id obj = [dict objectForKey:key];
}
elapsed = [[NSDate date] timeIntervalSinceDate:start];
ototal+=elapsed;
NSLog (#"reading %lu objects off dictionary via objectForKey took: %10.4f seconds", keys.count, elapsed);
}
NSString *slower = (vtotal > ototal) ? #"valueForKey" : #"objectForKey";
NSString *faster = (vtotal > ototal) ? #"objectForKey" : #"valueForKey";
NSLog (#"%# takes %3.1f percent longer then %#", slower, 100.0 * ABS(vtotal-ototal) / MAX(ototal,vtotal), faster);
}