17

Since Objective-C 2.0, we've had @properties and autogenerated accessor methods. So today, what is the point of key-value coding? Under what circumstances is it preferable to write

[myObject setValue:foo forKey:@"bar"];

instead of writing

[myObject setBar:foo];

or even

myObject.bar = foo;

I keep seeing articles and documentation making use of KVC, but always in a way where it seems like simply using properties would be better. So why would I ever use KVC? Thanks for any and all insight.

Alex Basson
  • 10,967
  • 6
  • 29
  • 44

3 Answers3

25

It's almost never preferable to write out [myObject setValue:foo forKey:@"bar"] by hand, with a literal @"bar". We usually use KVC to access a property when we don't know which property we want to access until runtime.

One example is an outlet in a xib. When you connect a text field's delegate outlet to the File's Owner in the xib, the xib records the connection as an object with three fields:

  • a reference to the text field (the object that has the outlet)
  • a reference to the file's owner placeholder (the destination of the connection)
  • the name of the outlet as a string, @"delegate"

At runtime, the xib loader (part of the UIKit framework) deserializes the text field. Then it deserializes the connection object and uses it to establish the connection that you wired up in the xib. The xib loader has to set a property of the text field (the delegate property), but it doesn't know which property until it loads the xib at runtime, long after both your app and the UIKit framework were compiled.

Another example of not knowing which property to access until runtime is the (little-known) ability of Core Animation to animate a custom property of your CALayer subclass. Say you create a subclass of CALayer called PolygonLayer, with a property named sides. You can animate the sides property using a standard CABasicAnimation:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"sides"];
animation.fromValue = @3;
animation.toValue = @9;
animation.autoreverses = YES;
animation.duration = 1;
[myPolygonLayer addAnimation:animation forKey:@"sides"];

Presto, Core Animation will animate your layer's sides property from 3 to 9 and back. Yet the source code of Core Animation doesn't know anything about your sides property. (Check out this question for more details.)

There are times we use KVC even though we know the property at compile-time. One example is when we want to take advantage of extra work KVC will do for us. For example, if you have an NSArray full of Person objects, and you want to get an array of every person's first name, you could write this:

NSMutableArray *firstNames = [NSMutableArray array];
for (Person *person in people) {
    [firstNames addObject:person.firstName];
}

But this is a case where KVC has a feature that makes it simpler. If you access a property of an array using KVC, KVC will actually access that property of every element in the array for you:

NSArray *firstNames = [people valueForKey:@"firstName"];

Another example where we might use KVC even though we know the property at compile-time is when the property is not statically declared as part of the class. For example, each NSManagedObject (part of Core Data) dynamically gives itself properties based on whatever entity that instance of NSManagedObject is representing. You can access those properties using KVC, although generally we prefer to declare them in a subclass of NSManagedObject or in a category of NSManagedObject.

Community
  • 1
  • 1
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks! This is really helpful. Your second example reminds me of a simple Python list comprehension, which is a feature I'd been wishing Objective-C had. – Alex Basson Nov 14 '12 at 20:05
  • 1
    @AlexBasson In that case, you may want to read about [KVC collection operators](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE0). – rob mayoff Nov 14 '12 at 20:07
  • Holy Moly. This is awesome! Where has this been all my life? Many, many thanks. – Alex Basson Nov 14 '12 at 20:16
  • So actually, your last example is exactly why I asked the question in the first place. I'm working through *Core Data for iOS* by Isted & Harrington, and they keep using KVC to set attributes on generic NSManagedObjects fitting an NSEntityDescription. I get that they're trying to make a point (entities aren't objects), but in real life, the thing to do is to subclass NSManagedObject and use accessors, right? – Alex Basson Nov 15 '12 at 20:57
  • It's usually better to make a `NSManagedObject` subclass, both for convenient accessors and for type safety. – rob mayoff Nov 15 '12 at 21:59
  • When I write something like `myObject.bar = foo;` is there the KVC under the hood? I mean: are they two completely different things or `myObject.bar = foo` gets translated to `[myObject setValue:foo forKey:@"bar"];`? Thx. – superpuccio Nov 27 '20 at 15:53
  • Yes, those are two completely different things. If the `bar` property was declared using a normal `@property` declaration, the compiler turns `myObject.bar = foo` into `[myObject setBar:foo]` which is a normal Objective-C message dispatch, and the compiler generates an efficient implementation of the `setBar:` method that operates directly on the `_bar` instance variable without using string-based lookup. So `myObject.bar = foo` is normally much more efficient than `[myObject setValue:foo forKey:@"bar"]`. – rob mayoff Jun 08 '22 at 14:43
0

KVC can be useful if you are using Key Value Observers to detect value changes on an object. If you wanted to use KVO and @properties you would have to wrap every mutator method with:

[self willChangeValueForKey:@"bar"];
bar = foo;
[self didChangeValueForKey:@"bar"];
Andrew
  • 91
  • 3
  • 3
    This isn't necessary. If you write your setter as `- (void)setBar:(id)newValue { bar = newValue; }`, and then try to observe the `bar` key on the object, KVO will (at runtime) modify your object by wrapping the `setBar:` method with calls to `willChangeValueForKey:` and `didChangeValueForKey:`. That has nothing to do with the KVC `setValue:forKey:` pattern. – rob mayoff Nov 14 '12 at 20:00
0

Sometimes you don't know what property you want to set/get until run-time.
In this case you can use KVC by constructing the property key path as a string.

For example i have an object with multiple NSArray properties and i want to keep the last NSDate they were updated.

Let's say i have an array property called: comments and an array property called likes.

I define a properties: commentsLastModified and likesLastModified. when an array is updated (i have the property name as string), i use:

[object setValue:[NSDate date] forKey:[NSString stringWithFormat:@"%@%@", arrayString, @"LastModified"];
Or Arbel
  • 2,965
  • 2
  • 30
  • 40