355

What is the difference between objectForKey and valueForKey? I looked both up in the documentation and they seemed the same to me.

James Webster
  • 31,873
  • 11
  • 70
  • 114
Devoted
  • 177,705
  • 43
  • 90
  • 110

6 Answers6

407

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.

starball
  • 20,030
  • 7
  • 43
  • 238
Corey Floyd
  • 25,929
  • 31
  • 126
  • 154
  • 12
    valueForKey behaves differently for NSDictionary objects depending on whether the key starts with an @ symbol. – dreamlax Jun 30 '09 at 10:17
  • 61
    objectForKey: accepts any object as a key, not just strings. The only requirement is that the key support the NSCopying protocol. – Ashley Clark Jun 30 '09 at 14:57
  • Both of these points are definitely true. NSDictionaries are a special case and you don't have to use strings for keys. I didn't really go into subtleties of either as it seems that the asker just required a simplistic comparison. – Corey Floyd Jun 30 '09 at 17:37
  • 5
    I'm surprised no one has corrected this answer by pointing out valueForKey: technically does not give you access to the corresponding instance variable, but rather the accessor method that (could) manage the instance variable. – Dany Joumaa Oct 25 '12 at 03:49
  • 7
    Warning: valueForKey can be intensely slow - it's currently a major bottleneck in my iPad app, so slow that replacing it with a "standard" dictionary made the app noticeably faster. Something very wrong with KVC on iOS, and I'll never use it again - not worth the drop in performance, and having to re-write it the long way anyway. This was using NSString keys with NSString values on CALayers. Instruments showed that "CAObject_valueForKey" was 25% of total runtime (!) – Adam Jan 04 '13 at 02:30
  • 2
    @Adam That sounds scary. Have you tried it again since iOS7? If so, has things changed since? – Unheilig Mar 14 '14 at 00:19
  • However, sometimes, there is a practical difference which I don't understand clearly yet. `valueForKey` didn't work in SOGo-3.1.4's code trying to call an unavailable "method" `ASProtocolVersion` on the `context` object (`EXCEPTION: NAME:NSInvalidArgumentException REASON:-[WOContext ASProtocolVersion]: unrecognized selector sent to instance`), whereas `objectForKey` works (and is the usual way to query the `context` object elesewhere in the code). See https://github.com/inverse-inc/sogo/pull/217/files – imz -- Ivan Zakharyaschev Jul 20 '16 at 11:58
  • @Adam I think you got it wrong. When you instrument it - you need to distinguish the time taken by the CAObject_valueForKey, from the time it takes CALayer to actually retrieve value. KVC protocol is synchronous, so if CALayer must block on something before providing answers - the overall performance may seem slow, even if valueForKey itself is very fast. KVC is blazingly fast, and has been since introduction. Nothing to do with iOS/tvOS/MacOS --- it is deep within ObjC's runtime. It also provides means to do marvelous things otherwise impossible. – Motti Shneor Jul 04 '19 at 06:53
64

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.

dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • can you please give me the link of the documentation you mean. thank you. – Ali Amin Jan 23 '14 at 18:24
  • 5
    @عليامين: [It's right here](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html#//apple_ref/doc/uid/20000140-BBCIBCDJ) – dreamlax Jan 23 '14 at 22:09
21

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

Nick Locking
  • 2,147
  • 2
  • 26
  • 42
  • 5
    good to know that "valueForKey: with an unknown key will throw NSUnknownKeyException saying "this class is not key value coding-compliant for the key" – onmyway133 Aug 15 '14 at 08:52
  • This is simply not true with NSDictionary, and you're welcome to try this:NSLog(@"Z:%@", [@{@"X":@(10), @"Y":@(20)} valueForKey:@"Z"]); valueForKey will emit such exceptions on other classes that do not support a specified key - but for NSDictionary subclasses - you'll just receive a quiet nil. try this: – Motti Shneor Jul 04 '19 at 07:06
13

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Harjot Singh
  • 6,767
  • 2
  • 38
  • 34
2

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
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
0

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);
}
Motti Shneor
  • 2,095
  • 1
  • 18
  • 24