Wilcox Development Solutions Blog

Learning Cocoa Bindings With PyObjC

May 19, 2005

Mac OS X’s Cocoa frameworks provide a functionality called Cocoa Bindings, a way to eliminate code that simply connects your model object to your view object. Today, faced with writing the type of code Cocoa Bindings was invented to avoid, I decided to learn how to use this technology with PyObjC, and wrote about How It Works here…

Raising Interface

Like many bits of Cocoa, Cocoa Bindings is set up in Interface Builder.

Interface Builder with the Cocoa Bindings tab open, setting values for the controller based part of article (Bind To: CocoaBindingAppDelegate, Model Key Path: sliderValue)

Get Info on the object you want to bind, and go to the Bindings Inspector (Command-4)

In our case, we want to bind this slider to a member of the CocoaBindingsAppDelegate class, and we specify that class in the Bind To popup. The Model Key Path is, in this case, the name of the key to look up (sliderValue).

A Quick Aside

Cocoa Bindings is based on Key Value Coding (and its related concept Key Value Observing.) KVC allows Cocoa to access your member data. KVC is a concept used many places in Cocoa itself, scripting for example. The idea behind KVC is that you give Cocoa a “key” and it goes and looks up (or sets) the value for that key in your object.

Cocoa gives you two ways to implement KVC: you can implement certain named methods, or you can just provide the variable name itself as the key. If you are taking the first approach you must implement a getter and a setter method, and follow the following naming convention:

Setters: setXxxxx Getters: xxxxxxxx

So, for a key named sliderValue you would implement two selectors: setSliderValue() and sliderValue().

In the complete example we will show both ways of implementing KVC (and show that, when you can use it, going the “variable name is key” approach is much less work.)

Back To The Python

We now have to implement the sliderValue in our PyObjC class:

class CocoaBindingsAppDelegate(NibClassBuilder.AutoBaseClass): def init(self): self.sV = 0 return self def sliderValue(self): """sliderValue is a KVO method called by Cocoa Bindings when it needs to retrieve a value for a UI element's data (in this case, the top slider). This is a KVO method """ print "get sliderValue called (controller!)" return self.sV sliderValue = objc.accessor(sliderValue) #this is part of the pyobjc KVO interface too! Don't forget it! def setSliderValue_(self, value): """setSliderValue is a KVO method called by Cocoa Bindings when it needs to set a value for a UI element's data. This is a KVO method""" print "set sliderValue called (controller!)" self.sV = value setSliderValue_ = objc.accessor(setSliderValue_)

We implement the KVC methods for sliderValue, which are called by Cocoa Bindings when a control needs to retrieve its value (or the user changes a control’s value.)

There’s an oddity here. Notice the lines:

sliderValue = objc.accessor(sliderValue)

This seems to be a required PyObjC idiom. Without this KVC complains that: “KVO autonotifying only supports -set: methods that return void.”, however, with this idiomatic line these messages go away (and autonotifying works, as you’ll see if you download the example package.)

Also, in the init method, we make sure to set sV to 0. In Python variables (including member variables) are created dynamically, on demand, so you must create them beforehand because you don’t know what KVC method will be called first (the getter - which would generate an error if self.sV didn’t exist - or the setter - which would create create a member variable of self called sV if one didn’t exist already, and then set the value of that member variable (newly created or not) to the passed in data.) To avoid errors, its best to assign logical default values to your member variables in your init selector.

The Further Adventures Of KVC

The whole point of the MVC pattern is that your controller shouldn’t hold data permanently, but instead act as an intermediary between the view layer and the model layer, as much as possible. In the above example, however, we have the controller holding data… wouldn’t it be nice if we could separate that out into a model object?

Well, we can. Cocoa Bindings allows you to specify a “string of dot-separated keys that specify a sequence of object properties to traverse” (Source: Cocoa Bindings: Object Modeling). With this information, let’s see what the Cocoa Binding setting for the other slider in the IB window is…

Interface Builder with the Cocoa Bindings tab open, setting values for the model based part of article (Bind To: CocoaBindingAppDelegate, Model Key Path: model.sdata)

The Python code is as follows:

`class myModelObj(NSObject): def init(self): self.sdata = 0 return self

class CocoaBindingsAppDelegate(NibClassBuilder.AutoBaseClass): def init(self): self.model = myModelObj.alloc().init() return self`

As promised, this example takes advantage of KVC’s ability to use the variable name itself as key. First in the init method of the CocoaBindingsAppDelegate (our controller) we create an instance of the myModelObj (our model object). myModelObj makes a member variable sdata and set it to a valid default.

To resolve the “string of dot-seperated keys”, Cocoa Bindings first asks the CocoaBindingsAppDelegate instance for a model member, then it asks the model object for a member named sdata. Finding one, KVC assigns/retrieves the data from that member.

The Sequel: Or, Cocoa Bindings For The More Interested Beginner

While snippets of Python and screenshots from IB are cool and everything, I’ve made the entire source package available online. The package shows all the code I’ve talked about here, along with some features I haven’t (for example, remembering the values you quit the application with, letting you restore those values at a later date.)

Conclusion

Cocoa Bindings is a great example of the dynamics of Objective-C, a wonderful testament to the abilities of PyObjC, and a simply amazing way to reduce the amount of code you have to write, while it also gives you certain other features (like scriptability) for free!

Credits:


Written by Ryan Wilcox Chief Developer, Wilcox Development Solutions... and other things