Re: Basics of Cocoa Bindings
Re: Basics of Cocoa Bindings
- Subject: Re: Basics of Cocoa Bindings
- From: Ken Thomases <email@hidden>
- Date: Wed, 09 Sep 2015 01:45:50 -0500
On Sep 8, 2015, at 11:04 PM, Alex Hall <email@hidden> wrote:
> I've created a new thread, because I obviously don't understand nearly as much about Cocoa Bindings as I thought I did.
Bindings is a complex and advanced topic. It's built on top of other technologies, which you need to understand and be comfortable with first: Key-Value Coding and Key-Value Observing. You should start with Key-Value Coding and work with that until you're comfortable. Then work to understand Key-Value Observing. And only then move on to bindings.
> I think my biggest problem is what values/keys to enter where in the Bindings Inspector for my views and controller. I get that selection, selectedIndexes, objectValue, and so on are all properties of the controller, but when do you use them, and what others are commonly needed/where are they needed? When do you use a property name stored in the controller, versus one stored in your model, versus one stored in your view controller? When do you simply leave a path blank, versus needing to provide a value? Why is the "controller key" field sometimes unavailable, and sometimes not?
Some of this is just experience and familiarity with the frameworks. But a lot of it is not about some pat rules you follow. The answer is often "you enter the paths that lead to the information you want/need for your design/purpose".
Rather than asking a general question like this, ask about one specific case that actually confuses you. Keep asking for clarification of specific bits about that one case until you understand. Then move on to the next case, etc.
If you're not already aware of it, there's the Cocoa Bindings Reference document. It documents what bindings the Cocoa classes support and explains them a bit. There's also a section describing binding types and options. Unfortunately, that document is not always as clear or complete as we might like, but it can be helpful. It's a reference, though; don't read it through. Just use it look specific stuff up when the needs arises.
<https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CocoaBindingsRef/CocoaBindingsRef.html>
> Speaking of keys, values, and paths, in a recent email, Ken said:
>> Basically, these keyPathsForValuesAffecting<Key> methods should list the key paths of any properties used in the implementation of the computed property getter. They are the "inputs" to the computation of the computed property.
>
> But why are they needed? Is this method taking the place of something I might do in IB, or is this something different? Why would anything other than my view controller (where this method is implemented) care about *how* a computed property is computed? If I'm standing between Alice and Bob, and I can hear both of them but they're too far apart to hear each other, I'm the controller. Bob asks me to ask Alice her hame, so I do. What this computed property situation feels like is me then asking Alice for the story behind her name, information totally unrelated to the specific detail Bob needs, and not in any way affecting my ability to get the name or to tell Bob what it is.
Your example with Alice and Bob is passive. It uses a "pull" model. When Bob wants to know Alice's name he asks. But there are plenty of places in app programming that need an active approach, a "push" model. Bob asked Alice her name once in the past and painted it on a billboard. From then on, he sits idle, figuring his work is done. If and when Alice changes her name, and assuming Bob wants his billboard to have current information, there needs to be an active agent that informs Bob that Alice's name may have changed and he needs to ask for it again. And that active agent needs some way to learn that Alice has changed her name so he can do this informing of Bob.
This is the role of Key-Value Observing. So, you need to understand that and what it does.
For example, consider a Rectangle class with "width" and "height" as stored properties and "area" as a computed property. The area is the product of width times height so, of course, it changes when either width or height changes.
Some code asks the framework to inform object O when property "width" of some Rectangle object R changes. The class of object O has implemented a method to receive change notifications. It can do whatever it wants in response to such change notifications.
"Width" is a stored property of Rectangle. It only changes when something sends a message to R. (Assigning to the property is actually a method invocation under the hood, at least for dynamic properties.) The KVO machinery knows the Cocoa naming conventions and, when the observer is added, it hooks into all of the methods on R whose names match the naming conventions for methods which might change its width. (In particular, the -setWidth: method. That's Objective-C syntax, but a method of the same name exists under the hood for a Swift class.) When one of those methods is called on R, KVO sends change notifications to all of the observers that have been added to R for the width property.
Now consider: some code asks the framework to inform object O when property "area" of some Rectangle object R changes.
"Area" is a computed property; it doesn't "have" a persistent value, it can _produce_ a value on demand. It's basically just a method that returns a value. The code that computes that value is arbitrary and opaque to KVO. (*We* know that it consists of "return width * height", but there's no way for the KVO machinery to know that.) In particular, there's no call to a method whose name matches the naming conventions for one which might change the value of area. Nothing calls -setArea:. The area property will just "spontaneously" be different the next time something queries it after either width or height changes.
Now, how can KVO notify observers when such a computed property changes? One horrible(!) approach would be polling. It could query the value over and over and, if it changes, it can send change notifications. You'll be glad to hear that that's not how it works.
Another approach would be to force you, the implementer, to manually generate change notifications. You could surround every place where you modify width or height with willChangeValueForKey()/didChangeValueForKey() calls:
self.willChangeValueForKey("area")
width = someNewValue
self.didChangeValueForKey("area")
And the same for the height property.
You can use property observers to accomplish this somewhat more automatically:
dynamic var width: Double = 0 {
willSet { self.willChangeValueForKey("area") }
didSet { self.didChangeValueForKey("area") }
}
dynamic var height: Double = 0 {
willSet { self.willChangeValueForKey("area") }
didSet { self.didChangeValueForKey("area") }
}
Of course, that's extremely tedious and error prone.
Luckily, there's a better way. This is what the keyPathsForValuesAffecting<Key> method is for.
When something observes R for changes of its area property, KVO calls Rectangle.keyPathsForValuesAffectingValueForKey("area"). The default implementation of that method takes the key name ("area") and combines it with "keyPathsForValuesAffecting" to construct the method name keyPathsForValuesAffectingArea. It then checks if the Rectangle class responds to that method and, if so, calls Rectangle.keyPathsForValuesAffectingArea() to get a set of key paths. Now it adds its own internal observers of R for changes in any of the properties named by the key paths returned by that method. When any of those properties changes, it also emits change notifications for the area property.
Although you probably hate this system by now, it's really quite elegant. One of the nice things is that the specification of the things which result in a change of area is defined in one place, which can be put right next to the definition of the area property itself. They can be seen together and changed together. Contrast this with the approach of manually calling will/didChange… where tidbits about the area property were spread around to all of the other properties which affect area's value.
Also, this makes the mechanism extensible. Extensions can add computed properties to a class based on its existing properties and make them KVO-compliant without having to muck about hooking into the existing properties.
> I'm currently binding my ArrayController to my ViewController. In the sample from Apple, though, they bound theirs to an ImageController class, if I recall the description of the figures correctly. Why did they do that instead of binding to a view controller, since they want to control a view? How did they get that controller to appear in the Bindings Inspector at all? Link:
> https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/HowDoBindingsWork.html
"GraphicController" in that document seems to just be the name assigned to an instance of NSObjectController in the NIB. You can assign your own labels to objects in Interface Builder and they'll show up in the pop-ups, etc. They also seem to be using GraphicController as the name of a variable in the associated code snippets. Presumably it's an outlet connected to the NSObjectController in the NIB.
Also, they bound a view — a view which implements a custom binding — to that controller. So, it's not quite analogous to you binding your array controller to your view controller. (I mean, it's not entirely dissimilar, either, but your question focuses on one of the differences.)
Unfortunately, this document is a little outdated in that it shows the custom "angle" binding of that joystick view class being established in Interface Builder, as well as code. That used to be possible using IB plug-ins, but it no longer is. Custom bindings can only be set up in code.
> My current project tries to use a model to adjust the display of a table, through an ArrayController and ViewController. Obviously, this is too complex a starting task for me. What do you recommend for a simpler app, so I can have a better chance of getting it working and seeing how it's supposed to work? I thought of something like making one edit field mimic another, so as I type into either one, the other updates. There's a whole edit protocol, though, that I'm not sure will help; I don't want it to introduce a new layer of complexity, I just want something simple to begin with.
You can do that. You can ignore the NSEditor and NSEditorRegistration protocols for now. For the text fields, though, you need to pay attention to the binding options and when the text field sends its view-initiated update of its value to the model. Hint: experiment with the Continuously Updates Value option.
There's a couple of examples in the Cocoa Bindings Programming Topics, one for connecting a slider with a text field and a more complex one with an attacker and a weapon choice.
However, to keep you motivated may I suggest an example that's just a simplified version of your app. Make an app with a single array of Tweets. So, no dictionary mapping names to arrays. Don't bother with the computed property to pick the current array out of that dictionary. Just one array property of your view controller.
Start with the array having hard-coded contents. Five Tweets. Keep the Tweet class simple so it just has "from" and "text" properties or something like that.
Then, just get that array to show in a table view using bindings. Follow the Populating a Table View Using Cocoa Bindings article of the Table View Programming Guide for Mac.
<https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TableView/PopulatingViewTablesWithBindings/PopulatingView-TablesWithBindings.html>
Next, add an "Add a Tweet" button to the view and hook it up to an action method which adds a new Tweet to the array in a KVO-compliant manner. If you get it right, the table view will reflect the new Tweet.
> In his most recent email, Ken said:
>> So, the array controller to serve the table view will be bound to some coordinating controller (likely File's Owner in a window or view NIB, AppDelegate in the MainMenu NIB). If that's the TweetDataController, directly, then the model key path would be "currentTweetStream.tweets".
>
> Where did Files Owner or App Delegate enter the picture? I've been using my view controller so far, and it and shared user defaults are the only two I have when I look at the bindings inspector for my ArrayController. That is, I couldn't select my app delegate or anything else even if I wanted to, and I don't see how they fit in.
This may be because I think in terms of NIBs and maybe you're using a storyboard. (You kids and your newfangled gizmos! ;) You should focus on the "some coordinating controller" part of what I said. My parenthetical was just mentioning examples of common ways the appropriate coordinating controller could be represented _in a NIB_. In a storyboard, the appropriate coordinating controller would be your view controller.
Regards,
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