1. 程式人生 > >KVC和KVO

KVC和KVO

前言

在網上找到一篇對KVO講的比較好的文章,原帖位置:https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/,轉帖出來。

正文

Key-value coding and key-value observing are two formalized mechanisms that allow us to simplify our code by harnessing the dynamic and introspective properties of the Objective-C language. In this article, we’ll take a look at some examples on how to put this to use.

Observing Changes to Model Objects

In Cocoa, the Model-View-Controller pattern, a controller’s responsibility is to keep the view and the model synchronized. There are two parts to this: when the model object changes, the views have to be updated to reflect this change, and when the user interacts with controls, the model has to be updated accordingly.

Key-Value Observing helps us update the views to reflect changes to model objects. The controller can observe changes to those property values that the views depend on.

Let’s look at a sample: Our model class LabColor is a color in the Lab color spacewhere the components are L

a, and b (instead of red, green, and blue). We want sliders to change the values and a big rectangle that shows the color.

Our model class will have three properties for the components:

SELECT ALL@property (nonatomic) double lComponent;
@property (nonatomic) double aComponent;
@property (nonatomic) double bComponent;

Dependent Properties

We need to create a UIColor from this that we can use to display the color. We’ll add three additional properties for the red, green, and blue components and another property for the UIColor:

SELECT ALL@property (nonatomic, readonly) double redComponent;
@property (nonatomic, readonly) double greenComponent;
@property (nonatomic, readonly) double blueComponent;

@property (nonatomic, strong, readonly) UIColor *color;

With this, we have all we need for our class interface:

SELECT ALL@interface LabColor : NSObject

@property (nonatomic) double lComponent;
@property (nonatomic) double aComponent;
@property (nonatomic) double bComponent;

@property (nonatomic, readonly) double redComponent;
@property (nonatomic, readonly) double greenComponent;
@property (nonatomic, readonly) double blueComponent;

@property (nonatomic, strong, readonly) UIColor *color;

@end

The math for calculating the red, green, and blue components is outlined on Wikipedia. It looks something like this:

SELECT ALL- (double)greenComponent;
{
    return D65TristimulusValues[1] * inverseF(1./116. * (self.lComponent + 16) + 1./500. * self.aComponent);
}

[...]

- (UIColor *)color
{
    return [UIColor colorWithRed:self.redComponent * 0.01 green:self.greenComponent * 0.01 blue:self.blueComponent * 0.01 alpha:1.];
}

Nothing too exciting here. What’s interesting to us is that this greenComponentproperty depends on the lComponent and aComponent properties. This is important to key-value observing; whenever we set the lComponent property we want anyone interested in either of the red-green-blue components or the colorproperty to be notified.

The mechanism that the Foundation framework provides for expressing dependencies is:

SELECT ALL+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

and more specifically:

SELECT ALL+ (NSSet *)keyPathsForValuesAffecting<Key>

In our concrete case, that’ll look like so:

SELECT ALL+ (NSSet *)keyPathsForValuesAffectingRedComponent
{
    return [NSSet setWithObject:@"lComponent"];
}

+ (NSSet *)keyPathsForValuesAffectingGreenComponent
{
    return [NSSet setWithObjects:@"lComponent", @"aComponent", nil];
}

+ (NSSet *)keyPathsForValuesAffectingBlueComponent
{
    return [NSSet setWithObjects:@"lComponent", @"bComponent", nil];
}

+ (NSSet *)keyPathsForValuesAffectingColor
{
    return [NSSet setWithObjects:@"redComponent", @"greenComponent", @"blueComponent", nil];
}

We have now fully expressed the dependencies. Note that we’re able to do chaining of these dependencies. For example, this would allow us to safely subclass and override the redComponent method and the dependency would continue to work.

Observing Changes

Let’s turn toward the view controller. The NSViewController subclass owns the model object LabColor as a property:

SELECT ALL@interface ViewController ()

@property (nonatomic, strong) LabColor *labColor;

@end

We want to register the view controller to receive key-value observation notifications. The method on NSObject to do this is:

SELECT ALL- (void)addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context

This will cause:

SELECT ALL- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context

to get called on anObserver whenever the value of keyPath changes. This API can seem a bit daunting. To make things worse, we have to remember to call:

SELECT ALL- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

to remove the observer, otherwise our app will crash in strange ways.

For most intents and purposes, key-value observing can be done in a much simpler and more elegant way by using a helper class. We’ll add a so-called observation token property to our view controller:

SELECT ALL@property (nonatomic, strong) id colorObserveToken;

and when the labColor gets set on the view controller, we’ll simply observe changes to its color by overriding the setter for labColor, like so:

SELECT ALL- (void)setLabColor:(LabColor *)labColor
{
    _labColor = labColor;
    self.colorObserveToken = [KeyValueObserver observeObject:labColor
                                                     keyPath:@"color"
                                                      target:self
                                                    selector:@selector(colorDidChange:)
                                                     options:NSKeyValueObservingOptionInitial];
}

- (void)colorDidChange:(NSDictionary *)change;
{
    self.colorView.backgroundColor = self.labColor.color;
}

The KeyValueObserver helper class simply wraps the calls to -addObserver:forKeyPath:options:context:-observeValueForKeyPath:ofObject:change:context: and -removeObserverForKeyPath: and keeps the view controller code free of clutter.

Tying it Together

The view controller finally needs to react to changes of the La, and b sliders:

SELECT ALL- (IBAction)updateLComponent:(UISlider *)sender;
{
    self.labColor.lComponent = sender.value;
}

- (IBAction)updateAComponent:(UISlider *)sender;
{
    self.labColor.aComponent = sender.value;
}

- (IBAction)updateBComponent:(UISlider *)sender;
{
    self.labColor.bComponent = sender.value;
}

The entire code is available as a sample project on our GitHub repository.

Manual vs. Automatic Notification

What we did above may seem a bit like magic, but what happens is that calling -setLComponent: etc. on a LabColor instance will automatically cause:

SELECT ALL- (void)willChangeValueForKey:(NSString *)key

and:

SELECT ALL- (void)didChangeValueForKey:(NSString *)key

to get called prior to or after running the code inside the -setLComponent:method. This happens both if we implement -setLComponent: and if we (as in our case) choose to auto-synthesize the accessors for lComponent.

There are cases when we want or need to override -setLComponent: and control whether change notifications are sent out, like so:

SELECT ALL+ (BOOL)automaticallyNotifiesObserversForLComponent;
{
    return NO;
}

- (void)setLComponent:(double)lComponent;
{
    if (_lComponent == lComponent) {
        return;
    }
    [self willChangeValueForKey:@"lComponent"];
    _lComponent = lComponent;
    [self didChangeValueForKey:@"lComponent"];
}

We disable automatic invocation of -willChangeValueForKey: and -didChangeValueForKey:, and then call it ourselves. We should only call -willChangeValueForKey: and -didChangeValueForKey: inside the setter if we’ve disabled automatic invocation. And in most cases, this optimization doesn’t buy us much.

If we modify the instance variables (e.g. _lComponent) outside the accessor, we need to be careful to similarly wrap those changes in -willChangeValueForKey:and -didChangeValueForKey:. But in most cases, the code stays simpler if we make sure to always use the accessors.

Key-Value Observing and the Context

There may be reasons why we don’t want to use the KeyValueObserver helper class. There’s a slight overhead of creating another object. If we’re observing a lot of keys, that might be noticeable, however unlikely that is.

If we’re implementing a class that registers itself as an observer with:

SELECT ALL- (void)addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context

it is very important that we pass a context that’s unique to this class. We recommend putting:

SELECT ALLstatic int const PrivateKVOContext;

at the top of the class’ .m file and then calling the API with a pointer to this PrivateKVOContext as the context, like so:

SELECT ALL[otherObject addObserver:self forKeyPath:@"someKey" options:someOptions context:&PrivateKVOContext];

and then implement the -observeValueForKeyPath:... method like so

SELECT ALL- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == &PrivateKVOContext) {
        // Observe values here
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

This ensures that subclassing works. With this pattern, both superclasses and subclasses can safely observe the same keys on the same objects without clashing. Otherwise, we’ll end up running into odd behavior that’s very difficult to debug.

Advanced Key-Value Observing

We often want to update some UI when a value changes, but we also need to initially run the code to update the UI once. We can use KVO to do both by specifying the NSKeyValueObservingOptionInitial. That will cause the KVO notification to trigger during the call to -addObserver:forKeyPath:....

Before and After the Fact

When we register for KVO, we can also specify NSKeyValueObservingOptionPrior. This allows us to be notified before the value is changed. This directly corresponds to the point in time when -willChangeValueForKey: gets called.

If we register with NSKeyValueObservingOptionPrior we will receive two notifications: one before the change and one after the change. The first one will have another key in the change dictionary, and we can test if it’s the notification prior to the change or the one after, like so:

SELECT ALLif ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
    // Before the change
} else {
    // After the change
}

Values

If we need either the old or the new value (or both) of the key, we can ask KVO to pass those as part of the notification by specifying NSKeyValueObservingOptionNew and/or NSKeyValueObservingOptionOld.

This is often easier and better that using NSKeyValueObservingOptionPrior. We would extract the old and new values with:

SELECT ALLid oldValue = change[NSKeyValueChangeOldKey];
id newValue = change[NSKeyValueChangeNewKey];

KVO basically stores the values for the corresponding key at the point in time where -willChangeValueForKey: and -didChangeValueForKey: were called, respectively.

Indexes

KVO also has very powerful support for notifying about changes to collections. These are returned for collection proxy objects returned by:

SELECT ALL-mutableArrayValueForKey:
-mutableSetValueForKey:
-mutableOrderedSetValueForKey:

We’ll describe how these work further below. If you use these, the change dictionary will contain information about the change kind (insertion, removal, or replacement) and for ordered relations, the change dictionary also contains information about the affected indexes.

The combination of collection proxy objects and these detailed change notifications can be used to efficiently update the UI when presenting large collections, but they require quite a bit of work.

Key-Value Observing and Threading

It is important to note that KVO happens synchronously and on the same thread as the actual change. There’s no queuing or run-loop magic going on. The manual or automatic call to -didChange... will trigger the KVO notification to be sent out.

Hence, we must be very careful about not making changes to properties from another thread unless we can be sure that everybody observing that key can handle the change notification in a thread-safe manner. Generally speaking, we cannot recommend mixing KVO and multithreading. If we are using multiple queues or threads, we should not be using KVO between queues or threads.

The fact that KVO happens synchronously is very powerful. As long as we’re running on a single thread (e.g. the main queue) KVO ensures two important things.

First, if we call a KVO compliant setter, like:

SELECT ALLself.exchangeRate = 2.345;

we are guaranteed that all observers of exchangeRate have been notified by the time the setter returns.

Second, if the key path is observed with NSKeyValueObservingOptionPrior, someone accessing the exchangeRate property will stay the same until the -observe... method is called.

Key-Value Coding

Key-value coding in its simplest form allows us to access a property like:

SELECT ALL@property (nonatomic, copy) NSString *name;

through:

SELECT ALLNSString *n = [object valueForKey:@"name"]

and:

SELECT ALL[object setValue:@"Daniel" forKey:@"name"]

Note that this works for properties with object values, as well as scalar types (e.g. int and CGFloat) and structs (e.g. CGRect). Foundation will automatically do the wrapping and unwrapping for us. For example, if the property is:

SELECT ALL@property (nonatomic) CGFloat height;

we can set it with:

SELECT ALL[object setValue:@(20) forKey:@"height"]

Key-value coding allows us to access properties using strings to identify properties. These strings are called keys. In certain situations, this will give us a lot of flexibility which we can use to simplify our code. We’ll look at an example in the next section, Simplifying Form-Like User Interfaces.

But there’s more to key-value coding. Collections (NSArrayNSSet, etc.) have powerful collection operators which can be used with key-value coding. And finally, an object can support key-value coding for keys that are not normal properties e.g. through proxy objects.

Simplifying Form-Like User Interfaces

Let’s say we have an object:

SELECT ALL@interface Contact : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickname;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *city;

@end

and a detail view controller that has four corresponding UITextField properties:

SELECT ALL@interface DetailViewController ()

@property (weak, nonatomic) IBOutlet UITextField *nameField;
@property (weak, nonatomic) IBOutlet UITextField *nicknameField;
@property (weak, nonatomic) IBOutlet UITextField *emailField;
@property (weak, nonatomic) IBOutlet UITextField *cityField;

@end

We can now simplify the update logic. First, we need two methods that return us all model keys of interest to use and that map those keys to the keys of their corresponding text field respectively:

SELECT ALL- (NSArray *)contactStringKeys;
{
    return @[@"name", @"nickname", @"email", @"city"];
}

- (UITextField *)textFieldForModelKey:(NSString *)key;
{
    return [self valueForKey:[key stringByAppendingString:@"Field"]];
}

With this, we can update the text field from the model, like so:

SELECT ALL- (void)updateTextFields;
{
    for (NSString *key in self.contactStringKeys) {
        [self textFieldForModelKey:key].text = [self.contact valueForKey:key];
    }
}

We can also use a single-action method for all four text fields to update the model:

SELECT ALL- (IBAction)fieldEditingDidEnd:(UITextField *)sender
{
    for (NSString *key in self.contactStringKeys) {
        UITextField *field = [self textFieldForModelKey:key];
        if (field == sender) {
            [self.contact setValue:sender.text forKey:key];
            break;
        }
    }
}

Note: We will add validation to this later, as pointed out at Key-Value Validation.

Finally, we need to make sure the text fields get updated when needed:

SELECT ALL- (void)viewWillAppear:(BOOL)animated;
{
    [super viewWillAppear:animated];
    [self updateTextFields];
}

- (void)setContact:(Contact *)contact
{
    _contact = contact;
    [self updateTextFields];
}

And with this, our detail view controller is working.

Check out the entire project on our GitHub repository. It also uses Key-Value Validation as discussed further below.

Key Paths

Key-value coding also allows you to go through relations, e.g. if person is an object that has a property called address, and address in turn has a property called city, we can retrieve that through:

SELECT ALL[person valueForKeyPath:@"address.city"]

Note that we’re calling -valueForKeyPath: instead of -valueForKey: in this case.

Key-Value Coding Without @property

We can implement a key-value coding-compliant attribute without @propertyand @synthesize / auto-synthesize. The most straightforward example would be to simply implement the -<key> and -set<Key>: methods. For example, if we want to support setting name, we would implement:

SELECT ALL- (NSString *)name;
- (void)setName:(NSString *)name;

This is straightforward, and identical to how @property works.

One thing to be aware of, though, is how nil is handled for scalar and struct values. Let’s say we want to support key-value coding for height by implementing:

SELECT ALL- (CGFloat)height;
- (void)setHeight:(CGFloat)height;

When we call:

SELECT ALL[object setValue:nil forKey:@"height"]

this would throw an exception. In order to be able to handle nil values, we need to make sure to override -setNilValueForKey:, like so:

SELECT ALL- (void)setNilValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"height"]) {
        [self setValue:@0 forKey:key];
    } else
        [super setNilValueForKey:key];
}

We can make a class support key-value coding by overriding:

SELECT ALL- (id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(id)value forUndefinedKey:(NSString *)key;

This may seem odd, but it allows a class to dynamically support certain keys. Using these two methods comes with a performance hit, though.

As a side note, it is worth mentioning that Foundation supports accessing instance variables directly. Use that feature sparingly. Check the documentation for +accessInstanceVariablesDirectly. It defaults to YES, which causes Foundation to look for an instance variable called _<key>_is<Key><key>, or is<Key>, in that order.

Collection Operators

An oft-overlooked feature of key-value coding is its support for collection operators. For example, we can get the maximum value from an array with:

SELECT ALLNSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);

or, if we have an array of Transaction objects that have an amount property, we can get the maximum amount with:

SELECT ALLNSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);

When we call [a valueForKeyPath:@"@max.amount"], this will call -valueForKey:@"amount" on each element in the array a and then return the maximum of those.

Apple’s documentation for key-value coding has a section called Collection Operators that describes this in detail.

Key-Value Coding Through Collection Proxy Objects

We can expose collections (NSArrayNSSet, etc.) in the same way as normal objects. But key-value coding also allows us to implement a key-value coding-compliant collection though proxy objects. This is an advanced technique. We’ll rarely find use for it, but it’s a powerful trick to have in the tool chest.

When we call -valueForKey: on an object, that object can return collection proxy objects for an NSArray, an NSSet, or an NSOrderedSet. The class doesn’t implement the normal -<Key> method but instead implements a number of methods that the proxy uses.

If we want the class to be able to support returning an NSArray through a proxy object for the key contacts, we could implement:

SELECT ALL- (NSUInteger)countOfContacts;
- (id)objectInContactsAtIndex:(NSUInteger)idx;

Doing so, when we call [object valueForKey:@"contacts”], this will return an NSArray that proxies all calls to those two methods. But the array will support allmethods on NSArray. The proxying is transparent to the caller. In other words, the caller doesn’t know if we return a normal NSArray or a proxy.

We can do the same for an NSSet and NSOrderedSet. The methods that we have to implement are:

NSArray NSSet                 NSOrderedSet             
-countOf<Key> -countOf<Key> -countOf<Key>
  -enumeratorOf<Key> -indexIn<Key>OfObject:
One of -memberOf<Key>:  
-objectIn<Key>AtIndex:   One of
-<key>AtIndexes:   -objectIn<Key>AtIndex:
    -<key>AtIndexes:
Optional (performance)    
-get<Key>:range:   Optional (performance)
    -get<Key>:range:

The optional methods can improve performance of the proxy object.

Using these proxy objects only makes sense in special situations, but in those cases it can be very helpful. Imagine that we have a very large existing data structure and the caller doesn’t need to access all elements (at once).

As a (perhaps contrived) example, we could write a class that contains a huge list of primes, like so:

SELECT ALL@interface Primes : NSObject

@property (readonly, nonatomic, strong) NSArray *primes;

@end



@implementation Primes

static int32_t const primes[] = {
    2, 101, 233, 383, 3, 103, 239, 389, 5, 107, 241, 397, 7, 109,
    251, 401, 11, 113, 257, 409, 13, 127, 263, 419, 17, 131, 269,
    421, 19, 137, 271, 431, 23, 139, 277, 433, 29, 149, 281, 439,
    31, 151, 283, 443, 37, 157, 293, 449, 41, 163, 307, 457, 43,
    167, 311, 461, 47, 173, 313, 463, 53, 179, 317, 467, 59, 181,
    331, 479, 61, 191, 337, 487, 67, 193, 347, 491, 71, 197, 349,
    499, 73, 199, 353, 503, 79, 211, 359, 509, 83, 223, 367, 521,
    89, 227, 373, 523, 97, 229, 379, 541, 547, 701, 877, 1049,
    557, 709, 881, 1051, 563, 719, 883, 1061, 569, 727, 887,
    1063, 571, 733, 907, 1069, 577, 739, 911, 1087, 587, 743,
    919, 1091, 593, 751, 929, 1093, 599, 757, 937, 1097, 601,
    761, 941, 1103, 607, 769, 947, 1109, 613, 773, 953, 1117,
    617, 787, 967, 1123, 619, 797, 971, 1129, 631, 809, 977,
    1151, 641, 811, 983, 1153, 643, 821, 991, 1163, 647, 823,
    997, 1171, 653, 827, 1009, 1181, 659, 829, 1013, 1187, 661,
    839, 1019, 1193, 673, 853, 1021, 1201, 677, 857, 1031,
    1213, 683, 859, 1033, 1217, 691, 863, 1039, 1223, 1229,
};

- (NSArray *)primes;
{
    return [self valueForKey:@"backingPrimes"];
}

- (NSUInteger)countOfBackingPrimes;
{
    return (sizeof(primes) / sizeof(*primes));
}

- (id)objectInBackingPrimesAtIndex:(NSUInteger)idx;
{
    NSParameterAssert(idx < sizeof(primes) / sizeof(*primes));
    return @(primes[idx]);
}

@end

We would be able to run:

SELECT ALLPrimes *primes = [[Primes alloc] init];
NSLog(@"The last prime is %@", [primes.primes lastObject]);

This would call -countOfPrimes once and then -objectInPrimesAtIndex:once with idx set to the last index. It would not have to wrap all the integers into an NSNumber first and then wrap all those into an NSArray, only to then extract the last object.

The Contacts Editor sample app uses the same method to wrap a C++ std::vector – in a contrived example. But it illustrates how this method can be used.

Mutable Collections

We can even use collection proxies for mutable collections, i.e. NSMutableArrayNSMutableSet, and NSMutableOrderedSet.

Accessing such a mutable collection works slightly differently. The caller now has to call one of these methods:

SELECT ALL- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;

As a trick, we can have the class return a mutable collection proxy through:

SELECT ALL- (NSMutableArray *)mutableContacts;
{
    return [self mutableArrayValueForKey:@"wrappedContacts"];
}

and then implement the correct methods for the key wrappedContacts.

We would have to implement both the method listed above for the immutable collection, as well as these:

NSMutableArray / NSMutableOrderedSet        NSMutableSet                             
At least 1 insertion and 1 removal method At least 1 addition and 1 removal method
-insertObject:in<Key>AtIndex: -add<Key>Object:
-removeObjectFrom<Key>AtIndex: -remove<Key>Object:
-insert<Key>:atIndexes: -add<Key>:
-remove<Key>AtIndexes: -remove<Key>:
   
Optional (performance) one of Optional (performance)
-replaceObjectIn<Key>AtIndex:withObject: -intersect<Key>:
-replace<Key>AtIndexes:with<Key>: -set<Key>:

As noted above, these mutable collection proxy objects are also very powerful in combination with key-value observing. The KVO mechanism will put detailed change information into the change dictionary when these collections are mutated.

There are batch-change methods (taking multiple objects) and ones that only take a single object. We recommend picking the one that’s the easiest to implement for the given task – with a slight favor for the batch update ones.

If we implement these methods, we need to be careful about automatic versus manual KVO compliance. By default, Foundation assumes automatic notifications and will send out fine-grained change notifications. If we choose to implement the fine-grained notifications ourselves through:

SELECT ALL-willChange:valuesAtIndexes:forKey:
-didChange:valuesAtIndexes:forKey:

or:

SELECT ALL-willChangeValueForKey:withSetMutation:usingObjects:
-didChangeValueForKey:withSetMutation:usingObjects:

we need to make sure to turn automatic notifications off, otherwise KVO will send out two notifications for every change.

Common Key-Value Observing Mistakes

First and foremost, KVO compliance is part of an API. If the owner of a class doesn’t promise that the property is KVO compliant, we cannot make any assumption about KVO to work. Apple does document which properties are KVO compliant. For example, the NSProgress class lists most of its properties to be KVO compliant.

Sometimes people try to trigger KVO by putting -willChange and -didChangepairs with nothing in between after a change was made. This will cause a KVO notification to be posted, but it breaks observers relying on the NSKeyValueObservingOld option. Namely this affects KVO’s own support for observing key paths. KVO relies on the NSKeyValueObservingOld property to support observing key paths.

We would also like to point out that collections as such are not observable. KVO is about observing relationships rather than collections. We cannot observe an NSArray; we can only observe a property on an object – and that property may be an NSArray. As an example, if we have a ContactList object, we can observe its contacts property, but we cannot pass an NSArray to -addObserver:forKeyPath:... as the object to be observed.

Likewise, observing self doesn’t always work. It’s probably not a good design pattern, either.

Debugging Key-Value Observing

Inside lldb you can dump the observation info of an observed object, like so:

SELECT ALL(lldb) po [observedObject observationInfo]

This prints lots of information about who’s observing what.

The format is private and we mustn’t rely on anything about it – Apple is free to change it at any point in time. But it’s a very powerful debugging tool.

Key-Value Validation

On a final note, key-value validation is also part of the key-value coding API. It’s a consistent API for validation property values, but it hardly provides any logic or functionality on its own.

But if we’re writing model classes that can validate values, we should implement the API the way set forward by key-value validation to make sure it’s consistent. Key-value validation is the Cocoa convention for validating values in model classes.

Let us stress this again: key-value coding will not do any validation, and it will not call the key-value validation methods. Your controller will have to do that. Implementing your validation methods according to key-value validation will make sure they’re consistent, though.

A simple example would be:

SELECT ALL- (IBAction)nameFieldEditingDidEnd:(UITextField *)sender;
{
    NSString *name = [sender text];
    NSError *error = nil;
    if ([self.contact validateName:&name error:&error]) {
        self.contact.name = name;
    } else {
        // Present the error to the user
    }
    sender.text = self.contact.name;
}

The powerful thing is that we’re asking the model class (Contact in this case) to validate the name, and at the same time we’re giving the model class an opportunity to sanitize the name.

If we want to make sure the name doesn’t have any leading white space, that’s logic which should live inside the model object. The Contact class would implement the key-value validation method for the name property like this:

SELECT ALL- (BOOL)validateName:(NSString **)nameP error:(NSError * __autoreleasing *)error
{
    if (*nameP == nil) {
        *nameP = @"";
        return YES;
    } else {
        *nameP = [*nameP stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        return YES;
    }
}

The Contact Editor sample illustrates this in the DetailViewController and Contact class.