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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to