Chapter 22: Cocoa’s Biggest Trick: Key-Value Coding and Observing
There is no magic in Cocoa. It’s just C. But one particular trick borders on “magic,” and that’s key-value observing (KVO). This chapter explores how and when to use KVO, as well as its nonmagical cousin, key-value coding (KVC).
Key-value coding is a mechanism that allows you to access an object’s properties by name rather than by calling explicit accessors. This allows you to determine property bindings at runtime rather than at compile time. For instance, you can request the value of the property named by the string variable someProperty
using [object valueForKey:someProperty]
. You can set the value of the property named by someProperty
using [object setValue:someValue forKey:someProperty]
. This indirection allows you to determine the specific properties to access at runtime rather than at compile time, allowing more flexible and reusable objects. To get this flexibility, your objects need to name their methods in specific ways. This naming convention is called key-value coding, and this chapter covers the rules for creating indirect getters and setters and how to access items in collections and manage KVC with nonobjects. You also find out how to implement advanced KVC techniques such as Higher Order Messaging and collection operators.
If your objects follow the KVC naming rules, then you can also make use of key-value observing. KVO is a mechanism for notifying objects of changes in the properties of other objects. Cocoa has several observer mechanisms including delegation and NSNotification
, but KVO has lower overhead. The observed object does not have to include any special code to notify observers, and if there are no observers, KVO has no runtime cost. The KVO system adds the notification code only when the class is actually observed. This makes it very attractive for situations where performance is at a premium. In this chapter, you find out how to use KVO with properties and collections, and the trick Cocoa uses to make KVO so transparent.
You can find all the code samples in this chapter in the online files for this chapter in the projects KVC
, KVC-Collection
, and KVO
.
Key-Value Coding
Key-value coding is a standard part of Cocoa that allows your properties to be accessed by name (key) rather than by calling an explicit accessor. KVC allows other parts of the system to ask for “the property named foo
” rather than calling foo
directly. This permits dynamic access by parts of the system that don’t know your keys at compile time. This dynamic access is particularly important for nib file loading and Core Data in iOS. On the Mac, KVC is a fundamental part of the AppleScript interface.
The following code listings demonstrate how KVC works with an example of a cell that can display any object using valueForKeyPath:
.
KVCTableViewCell.h (KVC)
@interface KVCTableViewCell : UITableViewCell
- (id)initWithReuseIdentifier:(NSString*)identifier;
// Object to display.
@property (nonatomic, readwrite, strong) id object;
// Name of property of object to display
@property (nonatomic, readwrite, copy) NSString *property;
@end
KVCTableViewCell.m (KVC)
- (BOOL)isReady {
// Only display something if configured
return (self.object && [self.property length] > 0);
}
- (void)update {
NSString *text;
if (self.isReady) {
// Ask the target for the value of its property that has the
// name given in self.property. Then convert that into a human
// readable string
id value = [self.object valueForKeyPath:self.property];
text = [value description];
}
else {
text = @””;
}
self.textLabel.text = text;
}
- (id)initWithReuseIdentifier:(NSString *)identifier {
return [self initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:identifier];
}
- (void)setObject:(id)anObject {
_object = anObject;
[self update];
}
- (void)setProperty:(NSString *)aProperty {
_property = aProperty;
[self update];
}
KVCTableViewController.m (KVC)
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @”KVCTableViewCell”;
KVCTableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[KVCTableViewCell alloc]
initWithReuseIdentifier:CellIdentifier];
// You want the “intValue” of the row’s NSNumber.
// The property will be the same for every row, so you set it
// here in the cell construction section.
cell.property = @”intValue”;
}
// Each row’s object is an NSNumber representing that integer
// Since each row has a different object (NSNumber), you set
// it here, in the cell configuration section.
cell.object = [NSNumber numberWithInt:indexPath.row];
return cell;
}
This example is quite simple, displaying 100 rows of integers, but imagine if KVCTableViewCell
had animation effects or special selection behaviors. You could apply those to arbitrary objects without the object or the cell needing to know anything about the other. That’s the ultimate goal of a good model-view-controller (MVC) design, which is the heart of Cocoa’s architecture. (See Chapter 4 for more information on the MVC pattern.)
The update
method of KVCTableViewCell
demonstrates valueForKeyPath:
, which is the main KVC method you use in this example. Here is the important section:
id value = [self.object valueForKeyPath:self.property];
text = [value description];
In this example, self.property
is the string @”intValue”
and self.target
is an NSNumber
object representing the row index. So the first line is effectively the same as this code:
id value = [NSNumber numberWithInt:[self.object intValue]];
The call to numberWithInt:
is automatically inserted by valueForKeyPath:
, which automatically converts number types (int
, float
, and so on) into NSNumber
objects and all other nonobject types (structs, pointers) into NSValue
objects.
While this example utilizes an NSNumber
, the key take-away is that target
could be any object and property
could be the name of any property of target
.
Setting Values with KVC
KVC can also modify writable properties using setValue:forKey:
. For example, the following two lines are roughly identical:
cell.property = @”intValue”;
[cell setValue:@”intValue” forKey:@”property”];
Both of these will call setProperty:
as long as property
is an object. See the later section “KVC and Nonobjects” for a discussion on how to handle nil
and nonobject properties.
Methods that modify properties are generally called mutators in the Apple documentation.
Traversing Properties with Key Paths
You may have noticed that KVC methods have key
and keyPath
versions. For instance, there are valueForKey:
and valueForKeyPath:
. The difference between a key and a key path is that a key path can have nested relationships, separated by a period. The valueForKeyPath:
method traverses the relationships. For instance, the following two lines are roughly identical:
[[self department] name];
[self valueForKeyPath:@”department.name”];
On the other hand, valueForKey:@”department.name”
would try to retrieve the property department.name
, which in most cases would throw an exception.
The keyPath
version is more flexible, whereas the key
version is slightly faster. If the key is passed to me, I generally use valueForKeyPath:
to provide the most flexibility to my caller. If the key is hard-coded, I generally use valueForKey:
.
KVC and Collections
Object properties can be one-to-one or one-to-many. One-to-many properties are either ordered (arrays) or unordered (sets).
Immutable ordered (NSArray
) and unordered (NSSet
) collection properties can be fetched normally using valueForKey:
. If you have an NSArray
property called items
, then valueForKey:@”items”
returns it as you’d expect. But there are more flexible ways of managing this.
For this example, you create a table of multiples of two. The data model object keeps track of only the number of rows, not the actual results, but it provides the results as though it were an NSArray
. This project is available as KVC-Collection
in the sample code. Here is how to create it:
1. Create a new iPhone project in Xcode using the Model-Detail Application template with storyboard and automatic reference counting.
2. Select MainStoryboard.storyboard
and remove the Master View Controller and the Detail View Controller.
3. Drag a view controller from the library and set its class to KVCTableViewController
. Click-drag from the navigation controller to your new view controller and set the relationship to rootViewController
.
4. Add labels and buttons as shown in Figure 22-1. The hash marks (###) are separate labels from the titles.
Storyboard for KVC-Collection project
5. Delete the MasterViewController
and DetailViewController
source files.
6. Add a new source file using the UIViewController
template and name it RootViewController
. Do not use a XIB for its user interface.
7. In the storyboard, select the root view controller and show the assistant editor. Click-drag from the labels and buttons to RootViewController.h
to create the outlets shown in Figure 22-2. Click-drag from the View button to the table view controller and select the push segue.
KVC-Collection Root View Controller layout
The following code implements the example project and demonstrates KVC access to a collection. When you press the Add button, the number of items stored in DataModel
is incremented. When you press the View button, a table view is constructed to display the information in the DataModel
using KVC proxy collections in RootViewController
and KVC collection accessors in KVCTableViewController
. Following the code, we explain how both of these access mechanisms work.
RootViewController.h (KVC-Collection)
@interface RootViewController : UIViewController
@property (nonatomic, strong) IBOutlet UILabel *countLabel;
@property (nonatomic, strong) IBOutlet UILabel *entryLabel;
- (IBAction)performAdd;
@end
RootViewController.m (KVC-Collection)
- (void)refresh {
DataModel *model = [DataModel sharedModel];
// There is no property called “items” in DataModel. KVC will
// automatically create a proxy for you.
NSArray *items = [model valueForKey:@”items”];
NSUInteger count = [items count];
self.countLabel.text = [NSString stringWithFormat:@”%d”,
count];
if (count > 0) {
self.entryLabel.text = [[items objectAtIndex:(count-1)]
description];
} else {
self.entryLabel.text = @””;
}
}
- (void)viewWillAppear:(BOOL)animated {
[self refresh];
[super viewWillAppear:animated];
}
- (IBAction)performAdd {
[[DataModel sharedModel] addItem];
[self refresh];
}
KVCTableViewController.m (KVC-Collection)
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
// countOfItems is a KVC method, but you can call it directly
// rather than creating an “items” proxy.
return [[DataModel sharedModel] countOfItems];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @”Cell”;
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
DataModel *model = [DataModel sharedModel];
id object = [model objectInItemsAtIndex:indexPath.row];
cell.textLabel.text = [object description];
return cell;
}
DataModel.h (KVC-Collection)
@interface DataModel : NSObject
+ (DataModel*)sharedModel;
- (void)addItem;
- (NSUInteger)countOfItems;
- (id)objectInItemsAtIndex:(NSUInteger)index;
@end
DataModel.m
@interface DataModel ()
@property (nonatomic, readwrite, assign) NSUInteger count;
@end
@implementation DataModel
+ (DataModel*)sharedModel {
static DataModel *sharedModel;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ sharedModel = [DataModel new]; });
return sharedModel;
}
- (NSUInteger)countOfItems {
return self.count;
}
- (id)objectInItemsAtIndex:(NSUInteger)index {
return [NSNumber numberWithInt:index * 2];
}
- (void)addItem {
self.count++;
}
@end
Note how RootViewController
accesses the array of items from DataModel
:
NSArray *items = [model valueForKey:@”items”];
Normally, you’d expect this to call [DataModel items]
, but there is no such method. DataModel
doesn’t ever create an array. So where does this NSArray
come from?
DataModel
implements countOfItems
and objectInItemsAtIndex:
. These are very specially named methods. When valueForKey:
looks for items
, it searches for the following methods:
• getItems
or items
or isItems
—These are searched in order, and the first one found is used to return the value.
• countOfItems
and either objectInItemsAtIndex:
or itemsAtIndexes
—This is the combination you use in this example. KVC generates a proxy array that’s discussed shortly.
• countOfItems
and enumeratorOfItems
and memberOfItems
—This combination causes KVC to return a proxy set.
• An instance variable named _items
, _isItems
, items
or isItems
—KVC will directly access the ivar. You generally want to avoid this behavior. Direct instance variable access breaks encapsulation and makes the code more fragile. You can prevent this behavior by overriding +accessInstanceVariablesDirectly
and returning NO
.
In this example, valueForKey:
automatically generates and returns a proxy NSKeyValueArray
. This is a subclass of NSArray
, and you can use it like any other array, but calls to count
, objectAtIndex:
and related methods are forwarded to the appropriate KVC methods. The proxy caches its requests, making it very efficient. See the Key-Value Coding Programming Guide in the iOS Developer Library for the full set of methods you can implement for this form.
In this example, the property is items
, so KVC looks for countOfItems
. If the property were to have been boxes
, KVC would look for countOfBoxes
. KVC requires that you name your methods in a standard way so that it can construct these method names. This is why getters must begin with a lowercase letter.
For mutable collection properties, there are two options. You can use the mutator (property-changing) methods such as the following (again, see the Key-Value Coding Programming Guide for the full list):
- (void)insertObject:(id)object
inChildrenAtIndex:(NSUInteger)index;
- (void)removeObject:(id)object
inChildrenAtIndex:(NSUInteger)index;
Or you can return a special proxy object by calling mutableArrayValueForKey:
or mutableSetValueForKey:
. Modifying this object automatically calls the appropriate KVC methods on your object.
KVC and Dictionaries
Dictionaries are just a special kind of nested relationship. For most keys, calling valueForKey:
is the same as calling objectForKey:
(the exception is if the key begins with @
, which is used to refer to the NSDictionary
itself, if needed). This is a convenient way to handle nested dictionaries, because you can use valueForKeyPath:
to access arbitrary layers.
KVC and Nonobjects
Not every method returns an object, but valueForKey:
always returns an id
. Nonobject return values are wrapped in an NSValue
or NSNumber
. These two classes can handle just about anything from numbers and Booleans to pointers and structures. While valueForKey:
will automatically wrap scalar values into objects, you cannot pass nonobjects to setValue:forKey:
. You must wrap scalars in NSValue
or NSNumber
.
Setting a nonobject property to nil
presents a special case. Whether doing so is permissible or not depends on the situation, so KVC does not guess. If you call setValue:forKey:
with a value of nil
, the key will be passed to setNilValueForKey:
. You need to override this method to do the right thing if you want to handle setting nil
for a nonobject property. Its default behavior is to throw an exception.
Higher-Order Messaging with KVC
valueForKey:
is filled with useful special cases, such as the fact that it’s overridden for collections like NSArray
and NSSet
. Rather than operating on the collection, valueForKey:
is passed to each member of the collection. The results are added to the returned collection. This allows you to easily construct collections from other collections such as
NSArray *array = [NSArray arrayWithObjects:@”foo”,
@”bar”, @”baz”, nil];
NSArray *capitals =
[array valueForKey:@”capitalizedString”];
This passes the method capitalizedString
to each item in the NSArray
and returns a new NSArray
with the results. Passing messages (capitalizedString
) as parameters is called Higher Order Messaging. Multiple messages can be passed using key paths:
NSArray *array = [NSArray arrayWithObjects:@”foo”,
@”bar”, @”baz”, nil];
NSArray *capitalLengths =
[array valueForKeyPath:@”capitalizedString.length”];
The preceding code calls capitalizedString
on each element of array
, then calls length
, and wraps the return into an NSNumber
object. The results are collected into a new array called capitalLengths
.
You looked at more flexible approaches using trampolines in Chapter 4, but KVC provides a very easy solution for many problems, as long as you don’t need to pass parameters.
Collection Operators
KVC provides a few complex functions as well. It can, for instance, sum or average a list of numbers automatically. Consider this:
NSArray *array = [NSArray arrayWithObjects:@”foo”,
@”bar”, @”baz”, nil];
NSUInteger totalLength =
[[array valueForKeyPath:@”@sum.length”] intValue];
@sum
is an operator that sums the indicated property (length
). Note that this can be hundreds of times slower than the equivalent loop:
NSArray *array = [NSArray arrayWithObjects:@”foo”,
@”bar”, @”baz”, nil];
NSUInteger totalLength = 0;
for (NSString *string in array) {
totalLength += [string length];
}
The performance issues are generally significant when dealing with arrays of thousands or tens of thousands of elements. Beyond @sum
, you can find many other operators in the Key-Value Coding Programming Guide in the iOS Developer Library. The operations are particularly valuable when working with Core Data and can be faster than the equivalent loop because they can be optimized into database queries. You cannot create your own operations, however.
Key-Value Observing
Key-value observing is a mechanism for transparently notifying observers of changes in object properties. At the beginning of the “Key-Value Coding” section, you built a table view cell that could display any object. In that example, the data was static. If you changed the data, the cell wouldn’t update. You improve that now. You can make the cell automatically update whenever its object changes. You need a changeable object, so use the current date and time. You use key-value observing to get a callback every time a property you care about changes.
KVO has a lot of similarities to NSNotificationCenter
. You start observing using addObserver:forKeyPath:options:context:
. To stop observing, you use removeObserver:forKeyPath:context:
. The callback is always observeValueForKeyPath:ofObject:change:context:
. Here are the modifications required to create 1,000 rows of date cells that automatically update every second.
KVCTableViewCell.m (KVO)
- (void)removeObservation {
if (self.isReady) {
[self.object removeObserver:self
forKeyPath:self.property];
}
}
- (void)addObservation {
if (self.isReady) {
[self.object addObserver:self forKeyPath:self.property
options:0
context:(__bridge void*)self];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ((__bridge id)context == self) {
// Our notification, not our superclass’s
[self update];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
- (void)dealloc {
if (_object && [_property length] > 0) {
[_object removeObserver:self
forKeyPath:_property
context:(__bridge void*)self];
}
}
- (void)setObject:(id)anObject {
[self removeObservation];
_object = anObject;
[self addObservation];
[self update];
}
- (void)setProperty:(NSString *)aProperty {
[self removeObservation];
_property = aProperty;
[self addObservation];
[self update];
}
KVCTableViewController.m (KVO)
#import “RNTimer.h”
@interface KVCTableViewController ()
@property (readwrite, retain) RNTimer *timer;
@property (readwrite, retain) NSDate *now;
@end
@implementation KVCTableViewController
- (void)updateNow {
self.now = [NSDate date];
}
- (void)viewDidLoad {
[self updateNow];
__weak id weakSelf = self;
self.timer =
[RNTimer repeatingTimerWithTimeInterval:1
block:^{
[weakSelf updateNow];
}];
}
- (void)viewDidUnload {
self.timer = nil;
self.now = nil;
}
...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @”KVCTableViewCell”;
id cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[KVCTableViewCell alloc]
initWithReuseIdentifier:CellIdentifier] autorelease];
[cell setProperty:@”now”];
[cell setObject:self];
}
return cell;
}
@end
In KVCTableViewCell
, you observe the requested property on your target in addObservation
. When you register for KVO, you pass self
as the context
pointer (after casting to void*
for ARC) so that in the callback you can determine if this was your observation. Because there’s only one KVO callback for a class, you may be receiving a callback for a property your superclass registered for. If so, you need to pass it along to super
. Unfortunately, you can’t always pass to super
because NSObject
will throw an exception. So you use a unique context
to identify your observations. There’s more about this in section “KVO Tradeoffs,” later in this chapter.
In RootViewController
, you create a property now
and ask the cell to observe it. Once a second, you update it. Observers are notified and the cells update. This is all quite efficient because, at any given time, there’s only about one screen of cells because of cell reuse.
The real power of KVO is seen in [KVCTableViewController updateNow]
:
- (void)updateNow {
self.now = [NSDate date];
}
The only thing you have to do is update your data. You don’t have to worry that someone might be observing you, and if no one is observing you, then you don’t pay any overhead as you would for NSNotificationCenter
. The incredible simplicity on the part of the model class is the real benefit of KVO. As long as you use accessors to modify your ivars, all the observation mechanism is handled automatically, with no cost when you don’t need it. All the complexity is moved into the observer rather than the observed. It’s no wonder that KVO is becoming very popular in low-level Apple frameworks.
KVO and Collections
Observing collections often causes confusion. The thing to remember is that observing a collection is not the same as observing the objects in it. If a collection contains Adam, Bob, and Carol, then adding Denise changes the collection. Changes to Adam do not change the collection. If you want to observe changes to the objects in a collection, you must observe those objects, not the collection. Generally, that’s done by observing the collection and then observing objects as they’re added, and stopping when they’re removed.
How Is KVO Implemented?
Key-value observing notifications rely on two NSObject
methods: willChangeValueForKey:
and didChangeValueForKey:
. Before an observed property change is made, something must call willChangeValueForKey:
. This records the old value. After the change is made, something must call didChangeValueForKey:
, which calls observeValueForKeyPath:ofObject:change:context:
. You can do this by hand, but that’s fairly uncommon. Generally, you do so only if you’re trying to control when the callbacks are made. Most of the time, the change notifications are called automatically.
There is very little magic in Objective-C. Even message dispatching, which can seem mysterious at first, is actually pretty straightforward. (Message dispatching is covered in Chapter 28.) However, KVO borders on magic. Somehow when you call setNow:
, there is an extra call to willChangeValueForKey:
, didChangeValueForKey:
, and observeValueForKeyPath:ofObject:change:context:
in the middle. You might think that this is because you synthesized setNow:
, and occasionally you’ll see people write code like this:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@”now”]; // Unnecessary
now_ = aDate;
[self didChangeValueForKey:@”now”]; // Unnecessary
}
This is redundant, and you don’t want to do it because then the KVO methods will be called twice. KVO always calls willChangeValueForKey:
before an accessor and didChangeValueForKey:
afterward. How? The answer is class swizzling. Swizzling is discussed further in Chapter 28, but when you first call addObserver:forKeyPath:options:context:
on an object, the framework creates a new KVO subclass of the class and converts the observed object to that new subclass. In that special KVO subclass, Cocoa creates setters for the observed properties that work effectively like this:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@”now”];
[super setValue:aDate forKey:@”now”];
[self didChangeValueForKey:@”now”];
}
This subclassing and method injection is done at runtime, not compile time. That’s why it’s so important that you name things correctly. KVO can figure this out only if you use the KVC naming convention.
It’s difficult to detect the KVO class swizzling. It overrides class
to return the original class. But occasionally you’ll see references to NSKVONotifying_MYClass
instead of MYClass
.
KVO Tradeoffs
KVC is powerful technology, but other than possibly being slower than direct method calls, it’s generally a good thing. The one major downside is that you lose compile-time checks of your property names. Always code following KVC naming conventions, whether or not you use KVC directly. Doing so will save you a lot of grief when you want to instantiate objects from a nib file, which requires KVC. It also makes your code readable by other Objective-C developers, who expect certain names to mean certain things. For the most part, this means naming your getters and setters property
and setProperty:
, respectively.
KVO, on the other hand, is a mixed bag. It can be useful, and it can cause trouble. It’s implemented in a highly magical way, and some of its usage is quite awkward. There’s only one callback method, which means you often wind up with a lot of unrelated code in that method. The callback method passes you a change dictionary that is somewhat tedious to use.
Because removeObsever:forKeyPath:context:
will crash if you’re not an observer for that key path, you must keep track of exactly which properties you’re observing. KVO has no equivalent to NSNotificationCenter removeObserver:
, which conveniently cleans up all observations you might have.
KVO creates subtle code-path surprises. When you call postNotification:
, you know that some other code may run. You can search your code for the notification name and generally find all of the things that might happen. It can be surprising that just setting one of your own properties can cause code in another part of the program to execute. It can be difficult to search the code to discover this interaction. KVO bugs in general are difficult to solve because so much of the activity “just happens,” without any visible code causing it.
So KVO’s greatest strength is also its greatest danger. It can sometimes dramatically reduce the amount of common code you write. In particular, it can get rid of the frequent problem of hand-building all your setters just so you can call an updateSelf
method. In this way, KVO can reduce bugs because of incorrectly cut-and-pasted code. But it can also inject really confusing bugs, and with the introduction of Automatic Reference Counting, handwritten setters are even easier to write correctly.
Several third parties have attempted to improve KVO. In particular, KVO is an obvious candidate for a block-based interface. In the “Further Reading” section at the end of this chapter, you’ll find links to some examples by very accomplished developers. Still, we’re very nervous about using a third-party solution here. KVO is complicated and magical. Blocks are complicated and magical. Combining the two without a large testing group seems extremely dangerous. Personally, we’ll wait for Apple to improve this interface, but if you’re interested in other options see the “Further Reading” section.
Our recommendation is to use KVO sparingly, simply, and only in places where doing so is a real benefit. For situations in which you need a very large number of observations (a few hundred or more), its performance scales much better than NSNotification
. It gives you the advantages of NSNotification
without modifying the observed class. And it sometimes requires less code, although you need to include all the special-case code you may need to work around subtle KVO problems. In the KVCTableViewCell
example, hand-coding setProperty:
and setTarget:
makes the file about 15 lines shorter than the equivalent KVO solution.
Avoid KVO in situations where you have complex interdependencies or a complicated class hierarchy. Simple solutions with delegates and NSNotification
are often better than excessively clever solutions using KVO.
On the other hand, Apple is clearly moving toward KVO in performance-critical frameworks. It’s the primary way to deal with CALayer
and NSOperation
. You can expect to see it more often in new low-level classes. It has the advantage of zero-overhead observation. If there are no observers of a given instance, then KVO costs nothing because there is no KVO code. Delegate methods and NSNotification
still have to do work even if there are no observers. For low-level, performance-critical objects, KVO is an obvious win. Use it wisely. And hope Apple improves the API.
Summary
In this chapter you have learned two of the most powerful techniques in Objective-C, KVC and KVO. These techniques provide a level of runtime flexibility that is difficult to achieve in other languages. Writing your code to conform to KVC is a critical part of a Cocoa program, whether you call valueForKey:
directly or not. KVO can be challenging to use well, but is a powerful tool when you need high-performance observations. As a Cocoa developer, you need to keep KVC and KVO in mind when designing your classes. Following a few simple naming rules will make all the difference.
Further Reading
Apple Documentation
The following documents are available in the iOS Developer Library at developer.apple.com
or through the Xcode Documentation and API Reference.
Key-Value Coding Programming Guide
Key-Value Observing Programming Guide
NSKeyValueCoding Protocol Reference
NSKeyValueObserving Protocol Reference
Other Resources
Matuschak, Andy. “KVO+Blocks: Block Callbacks for Cocoa Observers.” This is a very promising category for adding block support to KVO. In our opinion, it hasn’t gotten the kind of testing time it needs to trust it in a complex project, but we definitely like the approach. It’s the foundation of several other wrappers.http://blog.andymatuschak.org/post/156229939/kvo-blocks-block-callbacks-for-cocoa-observers
Waldowski, Zachary. BlocksKit. An extensive set of blocks-based enhancements, including a KVO observations wrapper. Although we haven’t used it extensively enough to recommend it, this package has the most users and active development. It is the one we’d most likely use. Its KVO wrapper is based on Andy Matuschak’s code, among others.https://github.com/zwaldowski/BlocksKit
Wight, Jonathan. “Key-Value Observing Done Right (again).” Another interesting example, based on Andy Matuschak’s MAKVONotificationCenter
.http://toxicsoftware.com/kvoblocks/