Objective-C Lesson 13: Key-Value Coding

Accessing ivars is usually done through accessor methods or dot-notation. But as a means of indirection, there is another way—Key-Value Coding, or KVC. This is a means of setting and getting the values by using strings—specifically, NSString, and its corresponding methods, including the very useful stringWithFormat:.

Methods

KVC involves another layer of abstraction, in the form of two specific methods: -valueForKey: and -setValue:forKey:. These methods work as they do for NSDictionary (which, unsurprisingly, is built upon the same principles). The key is an NSString, constructed as a constant or with any one of NSString’s methods. The code is simple:

NSString *name = [myObject valueForKey:@“name”];

The key is in fact the same name as your variable. It follows a specific sequence of steps:

  1. First, it looks for a getter method with the same name. In the above example, it looks for the -name or -isName (for boolean values) methods; if they are found, it invokes that method and returns that result.
  2. If the getter methods are not found, the method looks for an instance variable by that name: name or _name.

This is a very important mechanism: -valueForKey: uses the metadata in the runtime stack to look into your custom objects. This ability is not available in other languages. Speaking of other languages, experienced Java programmers are familiar with the concepts of autoboxing—converting int and other scalar types into corresponding object types, such as Integer and Double. KVC is the only Objective-C construct that supports autoboxing—it automatically converts scalar types to NSNumbers or NSValues. It will also do the opposite if you set one of these types to a scalar value. Note, however, you must still manually “box” a scalar value before using it in the setValue:ForKey: method:

[self setValue:[NSNumber numberWithFloat:249.42] forKey:@“price”];

If price is a float, the value will be automatically unboxed.

Key Paths

You can combine KVC and dot syntax to create a key path. Imagine that we have a store class; we can use the following syntax:

[store setValue:[NSNumber numberWithFloat:2.99] forKeyPath:@“gallonOfMilk.price];

That is the same as

store.gallonOfMilk.price = 2.99;

These key paths can be as deep as you want them to be.

Aggregated Results

NSArray *prices = [store valueForKeyPath:@“merchandise.price”];
// Merchandise is an array

If you ask an NSArray for a value for a key, it will create another array, ask each object for the value specified in the key path, and adds these values to the new array. It basically loops over each value in the array, and sends each object the method valueForKeyPath:@“price”.

Advanced Operators

  • @count: count = [store valueForKeyPath:@“merchandise.@count”]; The @count directive tells the KVC mechanism to get the count of the result of the rest of the key path.
  • @sum: totalPrice = [store valueForKeyPath:@“merchandise.@sum.price”]; The @sum directive gets the sum of all the requested values—in this case, the sum of all the prices of the merchandise.
  • @avg: avgPrice = [store valueForKeyPath:@“merchandise.@avg.price]; The @avg directive gets the average of all the values.
  • @min and @max:

    minPrice = [store valueForKeyPath:@“merchandise.@min.price”]; maxPrice = [store valueForKeyPath:@“merchandise.@max.price];

These two are self-explanatory: they return the maximum or minimum of all the values in the range. * @distinctUnionOfObjects: NSArray *brands = [store valueForKeyPath:@“merchandise.@distinctUnionOfObjects.brand”]; This key returns a list of individual distinct values—here, it returns a list of all the brands in our hypothetical store.

Note that, unfortunately, you cannot add your own operators.

One More Application

Imagine that you were creating a lottery application (or more likely, a lottery view within another application). You might have 10 views, each with a number, and you highlight each image as a number is drawn. In this case, you’d probably have ten instance variables, maybe named numberView1, numberView2, all the way to numberView10. To light up the views, you could have a large if…else or switch block, comparing the new value to all the values 1–10:

int nextNum = arc4random() % 10 } 1;    // Random number generator, 1–10
    switch (nextNum) {
        case 1:
            // Highlight
            break;
        // etc.
    }

Knowing KVC though, there is a more elegant solution:

[[self valueForKey:[NSString stringWithFormat:@“numberView%d”, nextNum]] highlight]; // Assume highlight method exists and works

That method can be found in this sample project: Find it here.

This post is part of the Learn Objective-C in 24 Days course.

0 comments


Or enter your name and Email
No comments have been posted yet.