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.

9781118449974-fg2201.tif

Figure 22-1 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.

9781118449974-fg2202.eps

Figure 22-2 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/

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.166.101