Re: Trouble binding against transient Core Data property
Re: Trouble binding against transient Core Data property
- Subject: Re: Trouble binding against transient Core Data property
- From: Quincey Morris <email@hidden>
- Date: Tue, 12 Oct 2010 16:15:17 -0700
On Oct 12, 2010, at 14:58, Rick Mann wrote:
> I'm still struggling to understand how this stuff works. I mean, given a pair of KVO-compliant properties, I don't see why Cocoa can't handle it all for me with a single call to -bind:….
Because that's not what Cocoa bindings are. You're expecting a mechanism that keeps two KVC properties (in different objects) locked in synchronization. That might be useful, but that's not what bindings are for.
We need terminology for this, since "binding" is ambiguous. Let's say a "binding-definition" is a mechanism that allows two objects to be linked together, and that a "binding-link" is an actual connection between two specific objects. (This is like a class/instance distinction.)
In general, a binding-definition is a named non-KVC attribute of an object ("object1"), which connects some internal state of the object to a KVC attribute of another object ("object2"), so that object1's internal state tracks the object2's attribute, in a way that's up to object1 to determine. Since object1's attribute is not KVC, it doesn't have to follow any KVC rules. Since object's property is, it does.
Now, the BindingJoystick example is pretty much going to drive you insane if you try to understand it. I'm not sure that I do, but I believe this is what's happening:
-- It (a NSView subclass) implements 2 binding-definitions, "angle" and "offset".
-- It also implements 2 KVC properties, "angle" and "offset", which pretty much have nothing to do with the bindings.
> But since I want to get this to actually work, I'll try to do it their way. Problem is, I just don't get how that's supposed to be.
>
> I'm looking at the BindingsJoystick example. It subclasses NSView, and implements a -bind:… method for it:
>
> - (void)bind:(NSString *)bindingName
> toObject:(id)observableController
> withKeyPath:(NSString *)keyPath
> options:(NSDictionary *)options
> {
> if ([bindingName isEqualToString:@"angle"])
> {
> // observe the controller for changes -- note, pass binding identifier
> // as the context, so we get that back in observeValueForKeyPath:...
> // that way we can determine what needs to be updated.
> [observableController addObserver:self
> forKeyPath:keyPath
> options:nil
> context:AngleObservationContext];
>
> // register what controller and what keypath are
> // associated with this binding
> [self setObservedObjectForAngle:observableController];
> [self setObservedKeyPathForAngle:keyPath];
> // options
> angleValueTransformerName = [[options objectForKey:NSValueTransformerNameBindingOption] copy];
> allowsMultipleSelectionForAngle = NO;
> if ([[options objectForKey:NSAllowsEditingMultipleValuesSelectionBindingOption] boolValue])
> {
> allowsMultipleSelectionForAngle = YES;
> }
> }
>
> if ([bindingName isEqualToString:@"offset"])
> {
> [observableController addObserver:self
> forKeyPath:keyPath
> options:nil
> context:OffsetObservationContext];
>
> [self setObservedObjectForOffset:observableController];
> [self setObservedKeyPathForOffset:keyPath];
> allowsMultipleSelectionForOffset = NO;
> if ([[options objectForKey:NSAllowsEditingMultipleValuesSelectionBindingOption] boolValue])
> {
> allowsMultipleSelectionForOffset = YES;
> }
> }
>
> [super bind:bindingName
> toObject:observableController
> withKeyPath:keyPath
> options:options];
>
> [self setNeedsDisplay:YES];
> }
I'm fairly certain this code is wrong (although, if wrong, wrong in a way that's fairly harmless). If the binding name is (say) "angle", the view (object1) is going to register 2 observations of the controller (object2). That means an KVO notification emanating from object2 is going to notify object1 twice. If you look at the rest of the code, you'll see that this is going to result in object1's "angle" KVO property getting set twice for every model-initiated change. That's wrong, but harmless.
Or, it's possible that this code has an actual purpose, by separating the code execution path for notifying object1 of a bindings-related change from a KVO-related change, to prevent a circularity. I don't really know.
> It then calls -bind:… on the joystick view like this:
>
> [joystick bind:@"angle"
> toObject:arrayController
> withKeyPath:@"selection.angle"
> options:options];
>
> In the joystick's bind, it explicitly sets up to observe changes in the angle on the arrayController's selection. But I don't get why they need to do that.
They didn't need to do that, I think. Really, the [super bind:...] should have been in an 'else' branch of a single big 'if'.
> Here's why:
>
> Analogously, I was calling -bind:… on my PlugIn model object. According to you
>
>> In your case, you reversed the order of the objects -- you "bound" the plugin's "frame" property to the view's "frame" property. In other words, you told your plugin object to KVO observe the view, and nothing more. That's why moving the view changes the model. But since it's uni-directional, changing the model (e.g. via undo) doesn't change the view.
>
> And I definitely had this behavior; the model noticed changes in the view. In other words, sending -bind:… makes the receiver observe the thing. If calling -bind:… on the joystick makes it observe the thing, then why does it explicitly observe it in its implementation, anyway?
Again, because the example code is wrong, or at least incredibly devious in a way that doesn't help understanding bindings.
> So, I've been preparing to implement -bind:… on my NSViewController subclass, rather than subclass NSView. I should be able to do this on either side of things, right? But I feel like it's redundant to observe the thing passed to you in bind, because I've demonstrated that that happens automatically (when you call -bind:…).
Adding a "frame" binding-definition to your view controller isn't really going to solve your problem. If you get it to work, it's going to synchronize the view controller with the data model, but that isn't going to get the view's frame synchronized. The correct place to implement the binding is in the view.
Earlier, I suggested you avoid bindings (as such) and just set up two observations in the view controller. Code in the view controller can then route the correct frame value to the appropriate object, depending on which object the frame change came from. You could also do this in the view itself.
> What all the examples seem to have in common is an NSObjectController (or subclass) fronting for the model object. I don't really have that, and I'm a little reluctant to add one (it's yet another object). I have to have an NSViewController, because there are potentially complex views associated with each PlugIn. I can add an NSObjectController that represents the PlugIn (the model object), but I tried this and it didn't work. It also rubs me the wrong way that NSObjectController has a notion of a "selection," which really isn't appropriate in this situation. I have a model and a view that I want bound to each other, and the view will never switch to refer to another model object.
Yes, the NS...Controller is a red herring, because it's there in the joystick example merely to permit the joystick inspector view to switch between model objects.
> So, I'm still unsure and uncomfortable with how this is supposed to be done.
>
> And in all this, I don't see how bindings fulfills the promise of relieving me from writing glue code.
If you can write a correct binding-definition in the correct object, then you should not need any additional glue code to make things work. Of course, the binding-definition code is *itself* glue code, so you won't be entirely free of glue code.
OK, I got all the way to the end and I forgot to say one important thing. When the first parameter to 'bind...' is *not* a binding-definition name, and *is* a KVC property name, then the implementation in -[NSObject bind...] happens to set up a one-way KVO observation. This is *nothing* to do with bindings.
Of all the mind-boggling aspects of this subject, that one is the bogglingest, so I'll say it again. When the first parameter to 'bind...' is not a binding name, the method has nothing to do with bindings, just with plain ol' KVO.
My advice: Forget you ever started thinking about bindings. Forget about the 'bind...' method. Go back to the basics. You have objects two classes that have a "frame" property that need to be kept in sync. Use 'addObserver:forKeyPath:...' and 'observeValueForKeyPath:...' in an object suitably placed to be an observer (e.g. your view controller, or possibly your view) of both. Write code to make sure that a change in one property is reflected as a change in the other. Forget that this thread ever existed. :)
_______________________________________________
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