Re: Implementing a KVO/Bindings-Compliant Bridge-Pattern in Cocoa
Re: Implementing a KVO/Bindings-Compliant Bridge-Pattern in Cocoa
- Subject: Re: Implementing a KVO/Bindings-Compliant Bridge-Pattern in Cocoa
- From: Ken Thomases <email@hidden>
- Date: Wed, 15 Oct 2008 05:56:57 -0500
On Oct 15, 2008, at 4:53 AM, Sebastian Morawietz wrote:
A Bridge object acts as a drop in for a Person-Object, with an
NSString* property called name and an Address* property address.
Binding to the keyPath "name" or "address" of the Bridge works nicely.
Trouble starts when binding some object to the keyPath
"address.street" of the bridge and a new Address-Object is set for
Person's address property. That results in KVO-related exceptions that
look like this:
* Cannot remove an observer <NSKeyValueObservance 0x126b00> for the
key path "street"
* from <Address 0x12f1d0> because it is not registered as an observer
On which line of testBindingToStreet() does the exception occur? What
is the stack trace?
I assume the Address object being mentioned in the exception is the
new one created in -[Person modifyAddress]. Have you confirmed that?
One of the problems I noticed is in -[Bridge
observeValueForKeyPath:...]. You are treating a key path as though it
were a key. will/didChangeValueForKey: don't take a key path; they
treat whatever is passed as a key, even if it contains a period.
(Note that there can be keys with embedded periods, like if a
dictionary has keys like "com.example.foo" or "John Q. Public".)
More seriously, observeValueForKeyPath:... doesn't call through to
super. Your use of KVB is implicitly using KVO, plus you're also
explicitly using KVO (from valueForUndefinedKey:). Your
observeValueForKeyPath:... only handles the latter. Therefore, you're
preventing the other observing code from coping with change
notifications.
On top of that, I think there's a fundamental mismatch between will/
didChangeValueForKey: and observeValueForKeyPath:... that you can't
account for. It should be the case that willChangeValueForKey: is
sent before the value changes and didChangeValueForKey: is sent
afterward. But observeValueForKeyPath:... is only invoked by KVO
after both of those are sent. You are turning around and trying to
use will/didChangeValueForKey: within your observeValueForKeyPath:...
You are pretending that the change is occurring during
observeValueForKeyPath:... when it's really happened already.
KVO is trying to switch of which "address" it's watching the "street"
property. But it's being told to make this switch after the change
has already occurred in the "obj" which Bridge holds. Bridge's
observeValueForKeyPath:... is invoked _after_ obj's address is done
changing. But during your explicit invocation of
willChangeValueForKey:, KVO dutifully asks Bridge for the value of its
"address" property in order to find out from which "address" it should
unregister interest in the "street" property. Bridge asks its "obj"
and gets the new Address, not the old one.
In order to do what you're trying to do, you'd need some way to hook
more directly into the will/didChangeValueForKey: machinery. I don't
know if it's possible. Alternatively, Bridge will have to cache its
own copy of each property of 'obj'. It will be out of sync briefly
with obj because Bridge will only be able to update its copy during
observeValueForKeyPath:..., but this is actually exactly what you need
to fix the problem. You would issue the willChangeValueForKey: call
before updating Bridge's copy, update the copy, then invoke
didChangeValueForKey: afterward. KVO would then have access to the
old address so it can unregister itself properly.
This is a hard problem. Also, I'm not really sure why you're
attempting to do what you're doing. The dynamism of KVC/KVO should
allow you to achieve your ends without the Bridge pattern. After all,
your bridge implementation requires that the wrapped object is already
KVC/KVO-compliant for the keys being accessed. So, I'm not sure what
you're hoping to gain. Can you explain what you need this for?
As an aside, here are a couple of nit-picks about the code:
* On 10.5 and later, you can use -forwardingTargetForSelector: for
simpler and faster forwarding. It's only documented (so far) in the
Leopard release notes <http://developer.apple.com/releasenotes/Cocoa/Foundation.html
>.
* Bridge's observedKeys should probably be a NSMutableSet rather than
an NSMutableDictionary, since you only care about the presence or
absence of keys and never about values.
Cheers,
Ken
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden