objective-c - tutorial - observevalueforkeypath not called




What is Key-Value-Coding and Key-Value-Observing in Objective C? (3)

Can someone explain in simple terms what is Key-Value-Coding and Key-Value-Observing? Please don't provide links to Apple Developer's reference Document. I have gone through them. I expect an explanation in very simple terms.


Start here.

Key-value coding is a mechanism for accessing an object’s properties indirectly, using strings to identify properties, rather than through invocation of an accessor method or accessing them directly through instance variables.


Key Value Coding is simply accessing a property of an object through a string instead of the literal syntax.

// Here is a new instance of an object
Foo *foo = [[Foo alloc] init];
// Accessing a property called someValue with literal syntax:
[foo someValue];
// Accessing the same property with dot notation
foo.someValue;
// Accessing the same property with Key-Value coding:
[foo valueForKey:@"someValue"];

The power of KVC is that you can specify any arbitrary string at runtime (obviously this could be very dangerous too).


Key-value coding allows you to fetch or change a property of an object using a string, at runtime, instead of needing to write code that is compiled to a fixed property from the start:

NSNumber* foo = [myPopup valueForKey: @"selectedItemIndex"];
[myPopup setValue: @15 forKey: @"selectedItemIndex"];

A good example for this is NSTableView on Mac, where you can just set an identifier on every table column that corresponds to your model object's property that it should display, and then your data source just calls -valueForKey:/-setValue:forKey: with the column's identifier as the key and the values pretty much display/set themselves. You just add the right columns to the table view in the XIB.

Key-value observing was added afterwards, and lets you register to be notified about changes made to another object. You register your interest by doing:

void*    gMyKVOContext = &gMyKVOContext; // global variable somewhere that guarantees us a unique address that doesn't collide with a subclass's registration for observing the same property

...

[interestingObject addObserver: interestedObject forKeyPath: @"interestingProperty" options: 0 context: gMyKVOContext];

Whenever that property is changed, -observeValueForKeyPath:ofObject:change:context: will be called on the object you specified as the observer. So you'd implement that like:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if( context == gMyKVOContext && [keyPath isEqualToString: @"interestingProperty"] )
    {
        // Update UI that shows interestingProperty
    }
    else
        [super observeValueForKeyPath: keyPath ofObject: object change: change context: context];
}

The advantage here is that you get called live the moment that other property is changed. Note that objects have to do a little work so these notifications are sent, so not all properties are key-value-observable. Also note that some objects may be in an invalid state if two related properties get changed right after the other: You get notified after the first property has been changed, which now contradicts the second, and only then the second property is changed and you're notified for that. So during that first observer callback, the object may be in a weird state, so be careful how you react to that.

To make a property observable, either use the default @synthesized implementation when you define it, or if you define it yourself, implement the setter like:

-(void)  setFoo: (int)inFoo
{
    [self willChangeValueForKey: @"foo"];
    _foo = inFoo;
    [self didChangeValueForKey: @"foo"];
}

Then always go through the setter to change it, don't change _foo directly. If you have related properties that could contradict each other like the above, a good way to avoid this is to always change them both in pairs (you can't use KVC then, though). To do that, implement a combined setter like:

-(void) setFoo: (int)inFoo bar: (int)inBar
{
    [self willChangeValueForKey: @"foo"];
    [self willChangeValueForKey: @"bar"];
    _foo = inFoo;
    _bar = inBar;
    [self didChangeValueForKey: @"bar"];
    [self didChangeValueForKey: @"foo"];
}

That way, both notifications are sent while the properties are in proper states.





objective-c