Key Value Coding – it’s one of the reasons WebObjects is so much fun.
The Basics
For those uninitiated, KVC allows your code to access method, attributes, or variables in your classes using string keys. When you are binding to a WOElement like WOString in WebObjects Builder you are using KVC (WOString, in-case you didn’t know, is a "String" with "WO"… in-joke, sorry). But KVC can help you a lot in your own code.
For instance, this:
public String myFirstName() { return person.firstName(); }
can be re-written as this:
Person person; // assume exists and implements NSKeyValueCoding public String myFirstName() { return (String)person.valueForKey("firstName"); }
You can also set the value on an object using:
public String setMyFirstName(String newName) { person.takeValueForKey(newName, "firstName"); }
Why bother? Well for one thing, when using KVC you don’t need to know the type of the object. So this code would work just as well:
EOEnterpriseObject eo; // assume exists public String myFirstName() { return (String)eo.valueForKey("firstName"); }
Meaning the eo
could be a Person, or an Employee, or a Customer, or any other object that returns a value for firstName
. This becomes very useful when you are writing reusable objects, handling conditional navigation in your components (ie: when the destination component will not be determined until run-time), or creating bindings for reusable components.
How does WebObjects find a match for your key? The KVC implementation in WebObjects will match using these rules:
- When "getting" values:
- a method named
firstName
orgetFirstName
- a variable named
firstName
or_firstName
- When "setting" values:
- a method named
setFirstName
- a variable named
firstName
or_firstName
Based on these rules, I like to name my accessors:
varName()
and their variables:_varName
so there is no confusion about which will be hit by KVC.
Following the Path
In addition to valueForKey
and takeValueForKey
, you can access values through their paths
using valueForKeyPath
and takeValueForKeyPath
. Lets say that you have an order that has an approver who has an address. You could access the address like this:
Order order; // assume exists public String approverAddress() { return (Address)order.valueForKeyPath("approver.address"); }
If you were in a WOComponent or some other object that implemented the NSKeyValueCoding interface, you could use code like this:
public String approverAddress() { return (Address)this.valueForKeyPath( "order.approver.address"); }
This example demonstrates why KVC takes me to my happy place: If any of the objects in the path are optional, KVC will gracefully handle the nulls
. So the code above could potentially replace:
public String approverAddress() { if (order != null) { Person approver = order.approver(); if (approver != null) { return approver.address(); } } return null; }
A Strong Foundation
Key Value Coding is implemented in many of the Foundation classes like NSArray, NSDictionary, etc. As an example, if you have an NSArray called orders that contains order objects that all need to be canceled by setting their cancelFlag to true. You could use:
NSArray orders; // assume exists public String cancelOrders() { orders.takeValueForKey(Boolean.TRUE, "cancelFlag"); }
Couple this with valueForKeyPath
and takeValueForKeyPath
you have a very powerful set of additional tools at your disposal.
Putting it all together
Here is a final example using KVC and the NSArray operators from ProjectWONDER that I mentioned previously here.
Perhaps we have this:
order <-->> product <-->> sku
We want to get all of the skus. Each order will have several products but not all products will have skus. Using traditional java we’d end up having to do something like this:
public NSArray allSkus() { NSArray products = order.products(); int productCount = products.count(); NSMutableArray allSkus = new NSMutableArray(); for (int i = 0; i < productCount; i ++) { Product product = products.objectAtIndex(i); NSArray theseSkus = product.skus(); if (theseSkus != null && theseSkus.count() > 0) { allSkus.addObjectsFromArray(theseSkus); } } return allSKus; }
Using KVC and the @flatten
opperator from ProjectWONDER we can do something like this:
public NSArray allSkus() { return (NSArray)order.valueForKeyPath( "products.skus.@flatten"); }
First we ask the order for its products, then we ask the products for their skus. KVC will discard any null results, and we end up with an NSArray of NSArrays of skus. The final @flatten operator will flatten this nested array into a single NSArray of skus.
Want the result sorted by skuNumber? No problem:
public NSArray allSkus() { NSArray allSkus = (NSArray)order.valueForKeyPath( "products.skus.@flatten"); return (NSArray) allSkus.valueForKeyPath( "@sortAsc.skuNumber"); }
Embracing Key Value Coding can reduce the code you write and make your code more robust. It can also help you work the "WebObjects Way".
I highly recommend it.
Note: If you feel uncomfortable implementing all of ProjectWONDER just to get the NSArray operators, don’t. Just add the ERXArrayUtilties.java file to your project and call ERXArrayUtilties.initialize(); in your Application’s constructor.
I love your excellent WO articles 😉 Just a little extra: if you dont have access to WONDER or dont want to use it, @flatten can be replaced simply by allSkus.removeObject(NSKeyValueCoding.NullValue) (assumed it is an NSMutableArray).
KVC rocks! 😉
Thanks Taurec. I’m glad you enjoy the posts, I enjoy writing them 🙂
The @flatten operator is actually pretty cool. It flattens multi dimensional arrays (ie: arrays of arrays of arrays) into a single dimensional array of objects. I’ve found it useful many times.
Another fun place where KVC becomes handy is to reduce the number of classes you have to create. For example, if I have an EO for a user (it holds their name, username, password, etc.) and I want them to be able to edit that, I’ll simply create an NSMutableDictionary and copy the values from the EO into the dictionary and then bind those to the various form fields. Because of KVC I can simply use an existing (and quite handy) foundation class so I don’t have to worry about creating a “tempory” or “worker” class. This saves loads of time.
Looking to get in touch with Galen Rhodes. The link above to Galen failed… my email is moc.camnull@kniwnairb