Difference between objectForKey and valueForKey? - objective-c

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

Related

NSObject not retaining

Process -
NSObject Class used to generate a card with certain properties. This is added to a MutableArray and used accordingly. However, after the function to determine the hand outcome in another class, the MutableArray loses all it's values.
Now I know a MutableArray simply points to the objects as opposed to holding them, so for it to lose all it's values I'm assuming the objects are being swept up by ARC.
-(void)rankHand {
NSString *echo = [Hand returnHandRank:_hand withString:false]; // 7 values in _hand
// 0 values in _hand.
NSLog(#"%#", echo);
}
After breakpointing to see the issue, the issue arises after returnHandRank: withString:
#interface Cards : NSObject
#property (nonatomic, strong) NSString *face;
#property (nonatomic, strong) NSString *suit;
#property (nonatomic, strong) NSString *symbol;
#property (nonatomic) int prime;
#property (nonatomic) int rankByInt;
+(NSMutableArray*)createDeck:(id)sender {
[sender removeAllObjects];
NSArray *faces = [[NSArray alloc] initWithObjects:#"A",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K", nil];
NSArray *suits = [[NSArray alloc] initWithObjects:#"h",#"d",#"c",#"s", nil];
NSArray *primes = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:41],[NSNumber numberWithInt:2],[NSNumber numberWithInt:3],[NSNumber numberWithInt:5],[NSNumber numberWithInt:7],[NSNumber numberWithInt:11],[NSNumber numberWithInt:13],[NSNumber numberWithInt:17],[NSNumber numberWithInt:19],[NSNumber numberWithInt:23],[NSNumber numberWithInt:29],[NSNumber numberWithInt:31],[NSNumber numberWithInt:37], nil];
for (int i = 0; i < 52; i++) {
Cards *card = [[Cards alloc]init];
card.face = [NSString stringWithFormat:#"%#", faces[i % 13]];
card.suit = [NSString stringWithFormat:#"%#", suits[i / 13]];
card.rankByInt = i % 13;
card.symbol = [Cards symbolForSuit:card.suit];
card.prime = [[primes objectAtIndex:(i % 13)] intValue];
[sender addObject:card];
}
[sender shuffle];
return sender;
}
Creates the _deck then _hand is filled by
[_hand addObject:[_deck objectAtIndex:0]];
[_hand addObject:[_deck objectAtIndex:1]];
[_hand addObject:[_deck objectAtIndex:3]];
[_hand addObject:[_deck objectAtIndex:4]];
[_hand addObject:[_deck objectAtIndex:5]];
[_hand addObject:[_deck objectAtIndex:7]];
[_hand addObject:[_deck objectAtIndex:9]];
returnHandRank: withString: is a very long function in the Hand class. So that is why I'm assuming they're not being retained.
Can anyone elaborate? I see it pointless to add the cards again from the _deck, it will it be the best solution?
EDIT: Added returnHandRank: withString:
+(NSString *)returnHandRank:(id)cards withString:(BOOL)returnString {
NSArray *combinations = [self returnCombinations];
cards = [self organizeCardsRankOrder:cards];
__block int maxRank = 0;
__block int maxValue = 0;
for (int i = 0; i < [combinations count]; i++) {
NSArray *splitString = [combinations[i] componentsSeparatedByString:#" "]; // splits the combination string.
NSArray *pointerArray = [[NSArray alloc] initWithObjects:
[NSNumber numberWithInt:[splitString[0] intValue]],
[NSNumber numberWithInt:[splitString[1] intValue]],
[NSNumber numberWithInt:[splitString[2] intValue]],
[NSNumber numberWithInt:[splitString[3] intValue]],
[NSNumber numberWithInt:[splitString[4] intValue]],
nil]; // turns the combinations into int values in an array.
NSMutableArray *fiveCardHand = [[NSMutableArray alloc] initWithObjects:
[cards objectAtIndex:[[pointerArray objectAtIndex:0] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:1] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:2] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:3] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:4] intValue]],
nil]; // Create the 5 card hand for the combination loop we are in, we'll now check this to see what rank it returns.
//Check for hand rank.
fiveCardHand = [self organizeCardsRankOrder:fiveCardHand];
NSArray *fiveCardHandOrganized = fiveCardHand;
int strength = [self handRankWithFiveCards:fiveCardHandOrganized];
if (strength > maxRank) {
maxRank = strength;
maxValue = 0;
}
int value = [self associateStrengthToHand:fiveCardHandOrganized andHand:strength];
if (value > maxValue) {
maxValue = value;
}
}
if (returnString) {
return [self handForStrengthWithStrength:maxRank];
} else {
return [NSString stringWithFormat:#"%i", maxValue];
}
}
There have been a few recent question involving combinations, so unless you are creating accounts we suspect there is homework afoot... No problem, let's see if we can point you in the right direction. However we cannot answer the question, not because it might be homework but because there is not sufficient information to do so.
Now I know a MutableArray simply points to the objects as opposed to holding them,
Correct so far...
so for it to lose all it's values I'm assuming the objects are being swept up by ARC.
but now completely wrong :-( You are misunderstanding how automatic memory management in Objective-C works. First forget "retain", modern ARC-based management is about ownership - whether a variable storing a reference asserts ownership over the object the reference references. When it does assert ownership the variable has the attribute strong, when it stores a reference but does not assert ownership then it has the attribute weak (there are some other ownership attributes you will come across later, they can be ignored for the moment). Object reference variables by default have the attribute strong.
Let's try an analogy:
Consider a balloon ("object"), it will float away unless it is held down; and a hand ("variable"), which holds things.
Many different hands can hold strings (references) attached to the same balloon.
If the hand holds a string tightly (strong) the ballon cannot float away.
If the string is just laying on the palm of the hand (weak) the ballon will float away unless at least one other hand is holding another string attached to the ballon tightly.
A balloon will not float away as long as at least one hand is holding a string tightly.
ARC is the breeze, it blows away balloons not held tightly.
An unannotated variable defaults to strong, so when a reference is stored in it the variable asserts ownership of the referenced object and it will not be cleared away by ARC. An instance variable of a class, or a standard (strong) property, all assert ownership. All the standard collections (arrays, dictionaries, sets) assert ownership over the objects referenced by the references stored in the collection.
Therefore, if you store a reference in an NSMutableArray the referenced object will not be cleared away by ARC as long as the reference remains in the array. If you mutate the array and remove a reference then the object referenced by it will be recycled (returned to the available memory pool) by ARC if and only if there are no other references to it stored in strong variables.
The array itself will stay around as long as a reference to it is stored in a strong variable. When there is no strong reference remaining to the array the array itself will be recycled by ARC, in the process all references stored in the array will be removed and if those references are the last strong ones to the referenced objects they too will be recycled.
Hope that helps and understanding how this works will help you find out where you are either emptying your array, or losing all strong references to the array itself; e.g. by assigning a new reference (or nil) to the variable(s) referencing the array.
Now let's look at some of your code:
NSArray *suits = [[NSArray alloc] initWithObjects:#"h",#"d",#"c",#"s", nil];
This is old style syntax, you can more easily create an NSArray using an array literal, #[ ... ]:
NSArray *suits = #[#"h", #"d", #"c", #"s"];
There are no NSMutableArray literals so you use an NSArray one an make a mutable copy: [#[ ... ] mutableCopy] or the shorter #[ ... ].mutableCopy (opinions differ on the use of the latter). There is also a literal for NSNumber objects, your code:
[NSNumber numberWithInt:41]
can simply be replaced by #41.
Using the above literals will make your code shorter and easier to read.
Now your statement:
card.face = [NSString stringWithFormat:#"%#", faces[i % 13]];
suggests a misunderstanding of how references and immutable objects work. An NSString object is immutable, once created its value will never change. The method stringWithFormat: constructs an NSString according to its format and arguments, which in this case is a single string, so you are just copying the string equivalent to:
card.face = [faces[i % 13] copy];
However a copy of an immutable object is just the original object. You know faces contains only immutable strings as you create it using string literals, so the above is equivalent to:
card.face = faces[i % 13];
Important: You can use a mutable, NSMutableString, reference as an NSString one by sub-classing, so the last step here dropping the copy is only valid if you know the reference is to an NSString object and not to an NSMutableString one.
Having used direct indexing on faces and suits you switch to long form:
card.prime = [[primes objectAtIndex:(i % 13)] intValue];
and in a few other places. All of them can be replaced by [...], e.g.:
card.prime = [[primes[i % 13] intValue];
While you uses of division (i / 13) and remainder (i % 13) are all correct you might want to consider using two nested loops to avoid them, e.g. something like:
for(int suitRank = 0; suitRank < 4; suitRank++)
{ for(int cardRank = 0; cardRank < 13; cardRank++)
{ // now use suitRank for i / 13 and cardRank for i % 13
The above is all just tidying up to make your code shorter, more readable, and less error prone. Now a more serious issue:
+(NSMutableArray*)createDeck:(id)sender {
[sender removeAllObjects];
Never do this! While id has it uses it reduces the compilers ability to check your code is correct and can result in your code going wrong when it is run for simple errors the compiler would have caught. Here sender is clearly meant to be a reference to a mutable array, declare it as such:
+ (NSMutableArray *)createDeck:(NSMutableArray *)sender
{
[sender removeAllObjects];
Later (after applying the above use of literals) you have:
NSMutableArray *fiveCardHand = #[ cards[[pointerArray[0] intValue]],
...
].mutableCopy;
//Check for hand rank.
fiveCardHand = [self organizeCardsRankOrder:fiveCardHand];
Here you:
create a mutable array
assign a reference to it to fiveCardHand
overwrite the reference in fiveCardHand with the result of organizeCardsRankOrder:
So here you appear not to have mutated the array referenced by fiveCardHand but instead changed the variable to reference a different array. You don't need to use mutable arrays to do that, you are mutating the variable holding the reference not the referenced array. Now "appear" was used here as you have not supplied the code of organizeCardsRankOrder:, maybe that method does mutate the array passed to it, if that is the case it does not need to also return it and there is no need for the assignment to the variable. So look at your code carefully here and decide whether you are mutating arrays or just variables and change it accordingly.
Finally you do not provide any declarations in the question for _deck and _hand. By naming convention you might be directly accessing the backing variable of a property (doing this is often best avoided), or accessing an instance variable, both of some unspecified class. Therefore we cannot provide any real help with these, just check that if they are connected to an instance that you are using the same instance everywhere you expect to - a common early error is to set an instance variable in one instance, try to read it from another instance, and then wonder why the value is different...
HTH, happy debugging!

# char as a key in NSDictionary

I tried using the # char as a key in a NSDictionary and my application simply crashes. I searched for "invalid" key names, could not find the #"#" anywhere. If I use something else than #"#" it all works fine.
I have a list of comanies, I get the first letter of each company and then I am creating a NSMutableDictionary entry containing the first letter as the key and a NSMutableArray as the value.
NSMutableDictionary indexDictionary = [[NSMutableDictionary alloc] init];
// here we have a loop on companyName
{
NSString *fristLetter = [[companyName substringToIndex:1] uppercaseString];
NSMutableArray *arrayOfIndexedCompanies = [indexDictionary valueForKey:firstLetter];
if (arrayOfIndexedCompanies) {
[arrayOfIndexedCompanies addObject:companyName]
}
else {
NSMutableArray *newArray = [NSMutableArray array];
[indexDictionary setObject:newArray forKey:firstLetter];
[newArray addObject:companyName];
}
}
I enabled breakpoint break on throw and it stops at the [indexDictionary valueForKey:firstLetter]... only when firstLetter is a #"#".
I had an if saying:
if ([firstLetter isEqualToString:#"#"]) {
firstLetter = #"A";
}
and this works fine, it places the # starting companies in the A section correctly. If I am letting the firstLetter unchanged (leaving it as #"#") the application will crash.
Also, this is not really my code, I am just trying to fix it, I am not quite familiar with ObjC and Foundation so please be gentle.
Read the documentation :)
Specifically, this bit :
If key does not start with “#”, invokes objectForKey:. If key does start with “#”, strips the “#” and invokes [super valueForKey:] with the rest of the key.
I guess that super valueForKey: isn't happy being called :)
To fix it, just call objectForKey: instead of valueForKey:
Using the string #"#" as a dictionary key works just fine:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:#"Test 1", #"#", #"Test 2", #"Another #", nil];
NSString *result = [dict objectForKey:#"#"];
NSLog(#"%#", result);
// --> Test 1
You could give a try with #"\u0040"

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

NSNumber as key for NSDictionary

I was wondering how keys work in a NSDictionary. Usually, I will use a NSString as a key for example:
NSString *stringKey = #"stringKey";
[mydict objectForKey:stringKey];
What if I wanted to use a NSNumber:
NSNumber *numberKey = [NSNumber numberWithInt:3];
[mydict objectForKey:numberKey];
Does the dictionary go look for the key with number 3? or would it just compare the address of the numberKey?
Two keys are equal if and only if [key1 isEqual:key2]. Some classes may go with the -[NSObject isEqual:] implementation of return self == other;, but it's quite common for classes (such as NSString, NSNumber, etc) to override it to do more context-specific comparison.
I think it will look at the value of NSNumber. i use NSNumber as key for dictionary.
1. store the key in view.tag
view.tag = [self getUniqueNSIntegerKey]; --> get a unique key and save in view.tag
[dictionary setOjbect: object forKey: [NSNumber numberWithInt:view.tag]]; // add the object
2. retrieve the object with the view.tag later
object = [dictionary objectForKey:[NSNumber numberWithInt:view.tag]]; // get the object via view.tag
it just works.....

How do you enumerate through an array in Obj-C?

I have an array which has several objects (all of different classes) in it. But using enumeration doesn't work on it for some reason.
NSString *arrayString;
NSURL *arrayUrl;
NSProcessInfo *arrayPr;
NSDictionary *arrayDictionary;
NSMutableString *arrayMString;
NSMutableArray *objectArray = [NSMutableArray arrayWithObjects:arrayString,arrayUrl,arrayPr,arrayDictionary,arrayMString,nil];
for( NSString *item in objectArray ){
NSLog(#"Class name is: %#", [item className]);
}
I think it might be something to do with how the objects are been added to the array but i'm new to objective-c and not sure.
you aren't actually populating the array.
NSString *arrayString;
declares a variable, arrayString, of type NSString. it's not initialized (so it deserves to crash when you use the variable -- but may be 0 with some build settings).
so, to assign a variable:
NSString *arrayString = [NSString stringWithFormat:#"sksjdhf %f\n", 3.3];
arrayWithObjects adds objects from the (va list) argument until nil/null/0 is encountered.
you must set up the remainder of your variables/arguments correctly before using them.
this should work as you expect it to:
NSString * str = #"a string";
NSMutableArray *objectArray = [NSMutableArray arrayWithObjects:str, nil];
for (NSObject * item in objectArray) {
NSLog(#"Class name is: %#", [item className]);
}
In the for loop, use an id data type. The id data type is a general purpose data type that can be used to store a reference to any object.
For example:
for ( id item in objectArray ) {
NSLog(#"Class name is: %#", [item className]);
}
Yep, that's how you do it. If you're having trouble, it is not in the enumeration syntax itself.