Application • 46:52
The Cocoa Bindings (the Controller Layer) introduced in Mac OS X Panther provides a new and easier way to manage user interfaces in Cocoa applications. View this session to learn about using and customizing bindings, and to see how this technology will evolve. This is an intermediate-level session.
Speaker: Ron Lue-Sang
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Cool. Good afternoon, everyone. Welcome to session 416. This session will be all about using and customizing Cocoa Bindings. My name... is Ron Lue-Saang. I'm a glue code architect at Apple. I work on the Cocoa Bindings technology, which we introduced last year at WWDC, and then shipped in Panther as the controller layer for Cocoa.
This year, I'd like to take the next hour or so and talk about four high-level topics, starting off with a general overview of Cocoa Bindings as a technology, and how it can speed up your application development and raise the quality of your applications. And then I'll talk about some of the new features that we've added to Cocoa Bindings since the last release. And then I'll finish off with some tips and tricks for using Cocoa Bindings in your everyday development, including a section I call "Roll Your Own Bindings," where we'll talk about making your View and Model subclasses more bindings-friendly. So let's-- Bless you.
So let's start off with the overview of Cocoa Bindings. If I've told you once, I've told you a thousand times, model view controller. So to understand Cocoa Bindings, you have to be familiar with Model-View-Controller. I'll say it again. You separate your application into three distinct layers-- the Model, View, and Controller layer.
And the Controller Layer is the part that keeps the whole application together, synchronizing the data from your Model Layer with the display logic in your View Layer. And that's why it's called Glue Code. Any Cocoa developer will look at this diagram and see, "Oh, View Layer Logic. AppKit helps me with that." And then you can see that the Model Layer Logic is built with foundation code, as well as now core data. Then the glue code is left up to you. You do that part. Well, you did until Cocoa Bindings. So now we have a reusable controller layer so that you can toss out all that glue code, writing much, much less code to get the same functionality.
Yeah, that's right. Oh, yeah. So, to give you an idea of exactly what the clicker does... There we go. So now Cocoa Bindings completes the picture of MVC the Cocoa way. It provides a reusable set of controller classes that abstracts the interaction with model layer objects. And also Cocoa Bindings brings with it the notion of a binding, which simply says what attribute of one object should be kept in sync with what property of another object. Simply a description of how to bind the values between the two objects.
So let me just go through the technology base on which Cocoa Bindings is built. There are three distinct protocols that are in Cocoa that are required for the Cocoa Bindings technology to work. The first, most of you are probably familiar with: key-value coding. Then there's Key-Value Observing, which we introduced last year, and Key-Value Binding. Key-value coding is an informal protocol that lets you access members of an object by name indirectly. That way you don't directly access it by instance variable or sending method messages directly.
Key Value Observing lets you describe how an object should be notified of changes from another object, so that an interested party can be notified when something changes in an observed object. Key-value binding provides a mechanism for establishing a binding, for creating that binding, that definition of what should be kept in sync with what other object.
[Transcript missing]
All of the values that are transferred between the layers are sent via KVC. So, key value coding messages are sent all the time to notify, to send values between the model and the controller, the controller and the view. And then Key-Value Observing is used for the view, for example, to register itself as an observer for the controller.
That way, any changes to the controller, like for its selection, the name of its selection, can be sent. A notification for that change can be sent to the view. This is how it knows that something's changed. Well, specifically, this is how it knows that something's changed. The view implements a method also defined via Key-Value Observing. The Observed Value for Keypath Method. This method is invoked on the View object, on the observer, whenever something in the controller, the observed object, changes.
Key-value binding lets you bind a view or multiple views to the same controller object. This is how the view knows that it should add itself as an observer of the controller. So during a typical edit cycle, when the user is actually interacting with the view, something changes in the view, and it will propagate the new value to the controller. The controller can then, on behalf of the view, send that new value onto the model object, thus keeping the view and the model in sync.
Furthermore, once something does change in the model, the model can tell the controller via key-value observing that something has changed. And again, the controller can notify any other views that are registered with the controller. This way, not only do the views stay in sync with the model objects, but views in the same view hierarchy can stay in sync with each other.
And all of this is done with the magic of Interface Builder. So you can actually configure these bindings in IB. It's not a code-- you can still configure bindings by code, but you've got IB. So I think next is a short demo of how exactly this looks in IB. We'll go to demo two, I believe.
So we have -- I have a simple nib. It's even called simple. An interface builder. Oh. Could I get the other other screen, please? One more. There we go. Thank you. Let's unclutter a bit. Here I have a simple window and another simple panel hooked up to a simple array controller.
Here you can see I've defined that the NSArrayController uses a mutable dictionary for its model objects, and that my table is bound to My array controller. And it's displaying the name of all the objects in the array controller. And also the preferred food of all the people displayed in the array controller. I'll just show you how this all runs. Just do test interface.
You can add a couple of items here. Let's go with... I like pizza. Let's say Andreas, who likes chocolate, I know. Bill, I think, is a sushi guy. So we can edit all of these values. And you'll see here that these text fields up at the top are kept in sync with the number of objects that are in the table, that are in the array. You can remove and add and remove and add and remove. And we also have multiple selection, so we can count how many things are selected. You can even edit other values, like the favorite language of each of these people.
I'm pretty sure Andres likes German. I think Bill likes foul language. I can't remember. So, and, you know, I can change the attributes of the text field here, the display attributes. So the text font size on a little -- this is supposed to be a preferences panel, but, yeah.
And even the text color -- oops. So, oh, there seems to be a break in the binding here. Let's go update that. So here you can see I have my text color binding bound to the shared user defaults. And here in the color well-- oh, it wasn't highlighted.
Well, you get the idea. The text color really does normally change. So we can do all of this stuff without writing any code, all in IB. We get a lot further developing our application just finishing the interface in Interface Builder. So this is what Cocoa Bindings will give you. If we can go back to the slides.
Tiger. OK, so that ends the demo. That's, also, the end of the overview section. How many of you guys have actually played with Cocoa Bindings already, by the way? Oh, yeah. How many of you have, like, never heard of Cocoa Bindings before? Raise your hands. It's OK. All right, OK. So then the overview isn't completely without merit. So that's the end of the overview.
Now we go into new features, things that we've added to Cocoa Bindings for Tiger. The first two features that I'll go over are the result of a lot of feature requests. The first will be a tree controller, so that now you have something to bind to from the outline view in the browser.
: And we also have filtering built into the NSA controller, so you don't have to subclass anymore. Glad you like it. I'll show you a little bit more about each of these. First, with the Tree Controller. With the Tree Controller, we wanted to make it as easy as possible to get used to using it. So, it kind of feels a little bit like using the Array Controller. Except, of course, Array Controllers manage arrays, but the Tree Controller manages a tree of objects.
But that tree isn't specifically any NS tree class, or you don't have to import your data into any specific class, or re-architect all of your objects. We just expect that you have what is probably natural for all of your data as far as tree goes. That you have arrays of objects that have arrays as children objects of arrays of arrays. So this graph up here pretty much sums up what the tree controller expects of your data.
If you can describe your data this way, then you can use your tree with the Tree Controller. All you have to do is just describe your tree. And the main thing for that description is the children key. If you set the Children key of the Tree Controller, That's the key the Tree Controller will use to traverse all of the nodes of your tree.
So this has the caveat that all of the objects in your tree have to respond to this children key, "key." This can usually be done by subclassing or by adding a category or changing the source. There are also some optional keys that you can set, the leaf key and the count key, which offer some optimizations for whether or not the tree controller should traverse children there. And then of course there are some actions that we expose for the Tree Controller. So you can bind buttons to it for adding siblings to the selection, as well as adding child objects to the selection, and removing any of the selected objects from the Tree Controller.
And then, after you've set all the keys and set up your Tree Controller, you need to tell it where the root of the tree is. You do this with either a content object binding, or by explicitly setting the content object the same way as an outlet, just using an outlet. From there, the Tree Controller will start managing the tree of objects.
There are also some extra keys that we've added. They look a lot like the Array Controller keys, starting off with the Arranged Objects, which returns the root of the tree after any sorting has happened to the child objects. Then there's also the Selection that knows how to manage intelligently multiple selection, no selection, not applicable, returning the appropriate key value binding markers.
And we've added a couple of keys just for convenience in Interface Builder: canInsertChild and canAddChild. We also have canInsert and canAdd in just plain form, so that now you can bind the enabled value of one of your buttons, so that if the tree controller doesn't support adding a child, then the button can stay disabled. And we've also added selection index paths as a controller key, which isn't the same as selection indexes. That's because we needed something different to identify nodes in the tree. You can think of the NSIndexPath, which we've added to Foundation, as the identifier for nodes in the tree.
It's similar to thinking about how you access elements of an array by index, except now we have a path of indexes that represents all the children of each node that you traverse through the tree. So here, 249 would indicate the child at index 9, of the child at index 4, of the child at index 2 of the root of your tree. It's a condensed way of representing each node in your tree.
So now that you have the Tree Controller, you can bind the NSOutlineView to the Tree Controller and the NSOutlineView's table columns. And that looks, again, exactly like binding a regular TableView table column to the Array Controller. You just bind the values or texts or font size or anything.
And then you bind the content and selection index paths of the OutlineView to the Tree Controller. The content binding gives the OutlineView all of the objects, basically the root object, to display. And then the selection index paths is how the OutlineView keeps its selection in sync with the Tree Controller.
And as I said, we also have support for NSBrowser, which has pretty much the same bindings, content and selection index paths, and then also a content values binding that you want to bind so that it'll actually get the value to display in the browser cell, rather than just displaying the description of the content, of any of the content objects.
After that, oh, one thing. If you go home and play with this tonight, try and hook up the outline view to the tree controller, and you go and start adding things, adding nodes to your tree, and start editing it. Editing isn't supported yet, sorry. That's coming. But you can still browse through the outline view and adjust the layout of the tree. That's still supported. And the next major feature is filtering in the NSRA Controller.
We've added filtering directly to the NSArrayController, so you don't have to subclass anymore to get filtering support. But in the process of adding filtering support to the array controller, we figured we'd add filtering support to NSArray. And we've also improved the synergy of using the array controller with the search field. And all of that comes to us thanks to the NS predicate. If you caught the core data talk earlier-- did anybody catch that talk? Yeah, good, good. With Core Data, we get a new class, the NSPredicate.
Basically, it's a class that describes a condition. And here, the string representation of one of these predicates, of a simple predicate, it looks a lot like the condition statement of a C-based language. So it's pretty easy to use. It's really easy to work with. And once you have a predicate, you can filter an array, get a filtered array from an unfiltered one using this method, filterArrayUsingPredicate, or filterMutableArrayInPlace using filterUsingPredicate.
And of course, you can filter an NSArrayController using setFilterPredicate. Once you have an array controller and you set its filter predicate, all of the arranged objects, the objects returned from the arranged objects accessor of the array controller, are the ones that match the condition defined in the filter predicate. There's also a controller key for the filter predicate, so you can bind objects that provide predicates to the ray controller directly.
And because of the way the filtering works, there might be strange user interaction. If, for example, someone has filtered out objects in a table view, and they go and add a new object into the array controller pressing the little Add button, and then it adds it, but immediately filters it out. That can be confusing to some users, and so we've added a new option to the array controller: clears filter predicate on insertion.
With this option turned on, which is by default, the array controller will clear out the filter predicate, setting it to nil, and then insert the new object. This should prevent a lot of confusion when inserting new objects by users. And just to clarify, remember that the arranged objects of the array controller is simply a filtered representation of the content of the array controller. So don't worry about it deleting the objects from your underlying array.
Search fields. So when we released Cocoa Bindings in Panther, we had search field support in the form of a recent searches binding. This gave you the ability to build search field menus much like the one that you'll find in Safari. With Tiger, we've added support for building search fields that look a lot like the one in Mail, so that you can select what you want to filter on, and then the user can type in the search criteria, and the filtering happens based on whether it's from or to or subject. And it's pretty simple. You simply bind the filter predicate, bind the predicate from the search field to the filter predicate of the array controller. And for every binding you make from the search field, you get another menu in the resulting search field.
And again, for every binding that you make, for every menu item, you have two options that you can set. The NSDisplayName binding option lets you set the name of the menu that'll show up in the search field. So this way it's easy to localize your nibs. And then NSPredicateFormat binding option gives you the ability to set the predicate format for that menu item. So defining that the From menu item should search on the From category of your model objects.
We've also added some integration with Core Data, speaking of NS predicates. What we've done is added support for Core Data-like functionality directly to the NS Object and NSArray Controller. in the form of...well, actually I'm getting ahead of myself. I should talk to you about Core Data first. The idea behind Core Data is that you define a description of your data, all of the model objects of your application. Rather than writing all of the classes, you define an entity. So the entity has all the information that describes the model objects of your application.
With that description, you can use an NSManagedObject context to manage managed objects. There's a lot of management going on. With the managed object context, though, it'll manage all of your I guess that's really the only way to say it. It'll try and keep a minimal set of objects that you're working with in memory. So this is a good way to do, like, as-needed faulting of your objects into memory space without you having to do much work. And it also manages storage and retrieval of your data, so it can handle saves and opens for you.
I think anyone who went to the Core Data Talk knows what I'm talking about. So you don't have to write any of that extra code in your NSDocuments subclass for saving and opening documents. And the way the managed object context gets these objects, and how you specify which objects it should manage, is by using a predicate. The predicate simply defines for the managed object context which objects to fetch.
With these three items, you can pretty much manage all of your model objects just through Core Data with a minimal amount of code work. And what we've done with the controllers is at the ability to define which entity the controller uses to which entity the controller manages really.
This is opposed to what is normally there, which is defining the object class. Now that you can define the entity, you can also define a fetch predicate, which is, in this case, different from the filter predicate of the array controller. This is simply the predicate that the controller should use to fetch objects. And there's also a way of setting the managed object context that the controllers will fetch into. You can do this either via binding or by an outlet in IB.
So with these three items, with these three pieces of information, the controllers can actually fetch for you through core data, just by knowing the entity, what fetch predicate to apply for the fetch, and which managed object context to fetch into. You can even set the controllers to fetch automatically for you on nib instantiate, or you can wire up an action from a button so that users can click the little fetch button as much as they like.
And something to note, when you're executing a fetch from the Is that clock right? When you're executing a fetch from the-- From the controller-- oh, into the object context, sorry. So when you're fetching, normally it's possible to define not only a fetch predicate, but also a sort ordering. But once you have an array controller, it's really better to sort in the UI. That's always going to be more correct.
Another thing is we've added a content set binding to the array controller so that you can bind a detail array controller, like an array controller that manages the relationship from a managed object. Because managed objects in core data have relationships that are held in sets, the array controller can't directly handle the relationship objects. So we've added the content set binding.
And another cool side effect of key-value observing, of building on those three KV-BLA technologies, KV-STAR technologies, with key-value observing, the controllers are always in sync with what's in the managed object context. So you get all of that magic for free, where if something is inserted into the context, it's automatically propagated up to the UI.
[Transcript missing]
Okay, so we'll toss out this simple example. We'll go to Xcode. First, I'll demo the Tree Controller. I have a simple app that I ripped off from one of my-- or borrowed from one of my coworkers. And it's a file system browser. Oh, I should show you that I don't actually have much code in here.
I have a couple of classes that actually represent the nodes in The nodes of the file system. So these actually go out and get the information of the files in the file system, and return it as a path component. And my main controller really has not much in it.
[Transcript missing]
So this is just a model object sort of thing. I'll turn off Zoom for a sec. And go back to the app. So this is just an outline view bound in the top to a tree controller, and a browser bound to the same tree controller in the bottom, as well as a text field that displays the selection.
So I can traverse through developer documentation. And you can see that the outline view selection is being kept in sync with the browser selection.
[Transcript missing]
For the filtering demo, I was going to run one application, but something's happened to it. So I'll skip that demo. I'll try and do something a little later if I have time. So I'll just go back to the slides now.
So, other new features. We've done a couple of other things beyond just the core NSController work. We've also added a binding debug mode to help you guys debug the binding problems you might run into. It's a pretty simple switch. There's a defaults write, NSBindingDebugLogLevel. Set that to 1. And any errors that the bindings encounter accessing or setting values or invoking methods will get logged for you, defining what was bound and what the error was. So you can track down if there were any configuration errors in your nib.
Another thing we've added is info for binding. This way you can get more information about a binding for any bound object. So if you have a bound view, a text field or something, and you invoke info for binding, you'll get a dictionary back of information relating to a specific binding for that text field. The dictionary has information like the object that it's bound to, which is usually its NSController, as well as the observed keypath, so the keypath that the view was bound with. And also the options key gives you back a dictionary of options that the view was bound with.
More new things on the NSArray Controller. If you've ever worked with the Multiple Values Selection Marker, It's a cool thing if you select multiple objects in an array controller, for example, and they all have the same name, and you want to display the name of the selection in the array controller.
The selection proxy, the object you get from asking the array controller for its selection, can actually return either the multiple selections marker or the common name for all the objects in the selection. The way it does this is it runs through all of the selected objects and compares whatever you're going to display for those objects. So if they all have the same name, then we'll display the name. Otherwise, we display the multiple selection marker placeholder.
This might be a little bit more information than you need. But the point is, now there's a new option to turn off that behavior so that any time that there's more than one item selected, we'll always return the multiple selection placeholder, the selection marker. So this can be a big optimization for selections that are very large where all the names might be similar, or all the values might be similar.
And we have other random options and bindings that we've added. NSView now has a tooltip binding. NSTextView has an attributed string binding, so now you don't have to convert from RTF data, blah, to get attributed strings into your text fields, your text views. NSWindow now has a display pattern title binding, so you can customize the display of the window based on multiple pieces of input from the controllers.
The table column now has a new option for turning off automatic creation of the sort descriptors. We automatically create sort descriptors for all of the table columns in your table. That way you get sorting for free. Some people don't like that. So now you can turn that off. And as I said, we have a new binding for the ArrayController content set for working with the managed objects relationships.
We've also added some changes to the way NSPopupButton and NSMatrix work. Specifically, we've added ContentObjects as a new binding. With ContentObjects, you can imagine If you bind the pop-up button to an array of objects as its content, and you want to synchronize the selected objects of the pop-up button, Normally, you would get back in that selected objects array all of the objects that are selected from the content array.
But you might actually be more interested in objects related to the objects in the content array. So now with content objects, you can define, for example, the name of all the people in-- the name of the people that you're displaying at the pop-up will become the selected objects. So you can target a different object in your pop-up. It's really simpler to use than to explain. Sorry.
NSMatrix has two modes that we support. Three modes, really. In radio mode, the default, we support single selection. So there are two bindings, the selected value and the selected object. When the matrix is in list or highlight mode, we now support multiple selection, so that you have the selected values and selected objects bindings available. This is simply a change that we had from last release. Oh, all right. New features, that's it. Now I'll go into some of the tips and tricks for building Cocoa friendliness into your own classes.
If you have model objects that you want to use with Cocoa Bindings, you're probably done. Usually that's all you need to do, as long as the attributes that you expect to be bindable are key-value coding compliant. So in other words, you have either a key-value coding compliant accessor for the attribute, or you allow direct IVAR access. Usually that's all you really need to do.
And part of that is that Key-Value Observing automatically sends out notifications that Cocoa Bindings requires. As long as the attributes that you're exposing is bindable, or Key-Value Coding compliant, Key-Value Observing does the notification generation for you. If for some reason you have other attributes that you want to expose as bindable that aren't key-value coding compliant, or you have some really complex logic that changes lots of values, lots of attributes of your model object all at once, then you can send out notifications manually.
Simply invoke self willChangeValueForKeyPath defining the key path it is about to change, change the value related to that key path, and then invoke self didChangeValueForKeyPath. This is the way that you send out key-value observing notifications manually. So as long as your object is key-value observing compliant, and you have some key-value coding compliant way of getting to the values, the next thing you would need to do-- oh, actually, this is on the wrong slide. Key-value binding exposing should be on this slide.
If you have a view that you want people to be able to bind from, so that it has those neat little attribute boxes show up in Interface Builder, all you need to do is pretend that those last two items are on the next slide. Exposed binding and exposed bindings are two methods that you can use on your class. They're defined in key-value-binding.h.
It lets you programmatically expose bindings for your class. That way, they show up in Interface Builder, as long as the class is loaded by a bundle or a pallet. Continuing with making your view Cocoa Bindings compliant, once you've exposed a binding of your view, You'll need to implement the bind-to and unbind methods.
The BindTo method is how you get the information of what your binding should do, of what to synchronize between your view and some controller object. Typically there you would simply cache the object you're binding to, as well as the key path and options dictionary that you're creating the binding with.
And immediately after, you would typically add yourself as the observer of the object you're being bound to. So your view would invoke self add observer or object that you're binding to, add observer self. So that way you'll get notifications in the form of observed value for keypath whenever something in the observed object changes.
Once you have that in place, if you have a view that's actually doing something that's editing related, editing a value rather than just display. You'll probably also want to implement the NS Editor and NS Editor registration methods. Well, really implement the NS Editor methods and know about the NS Editor registration methods.
NSEditor is an informal protocol that defines how the controller can tell your view that it should stop editing or discard editing. Commit editing is invoked on your view if you should try and end editing in your view, and then you can return a yes succeeded, or no, if there was some sort of verification validation error. Discard editing is sent when you should just throw out any changes that the user has made and revert to the previous value.
Once you start editing, once an editing session has started, like the user has started moving around something in your view, in your control, then you can invoke objected begin editing on the controller that you're bound to. You should be notified if it should commit edits or discard any edits. And then, of course, once editing is done, you can send the controller objected and editing.
Now once you have your view, and it's bindable and beautiful, then you should really put it on a pallet. That's the best way to take advantage of Cocoa Bindings, so that people who use your view, use your code, don't have to write the manual bind to blah blah blah methods in their classes.
Now, just going over again some of the basics of how Cocoa Bindings works with key-value coding, observing, and binding. When I say a key-value coding compliant accessor, I'm really talking about a name and set name kind of method pair. With that, your model object will be key-value coding compliant for the key name. So that way you can bind the sum attribute to an object that is of your object type with the keypath name.
That's all you need to know for that one. Oh, oh, that's true. Now, if you're a Vue, and you want to implement this bind method, again, what you would typically do in your implementation is cache away the observed controller, the keypath, and the binding options that are sent in during the binding creation.
And if you're going to support more than one binding, it's probably a good idea to cache these in a dictionary on a per-binding basis. This way when you get the key value observed, Observe value for keypath method invoked on your view. You can keep track of what attribute you're supposed to update in your view.
And here, I'd like to ask Andreas to come up to do a little demo. He doesn't know what I'm getting him into. So I think a lot of people are kind of confused about how Cocoa Bindings works, how the BindTo method works. Perfect. If we pretend that you guys are the developer of some application and I'm a view and Andreas is a controller-- not that I'm saying anything here-- in order for me to keep some attribute, say my-- I'd wanted to find a hat actually-- say which arm is up, if I wanted to keep that in sync with some value that Andreas can give me-- exactly, see? All you have to do is say, Ron, bind arm height to Andreas.
Ron, bind arm height to Andreas. Perfect. So it's something like that. So now, anything Andreas does, I can see and update my state. It's pretty simple, right? See, now the great thing is-- The great thing is, with bindings you can also add value transformers so that anything that I get from the controller can be changed before actually sent to the view. So if there was an NS negate boolean transformer, or negate arm height transformer, anything he did would be in reverse.
I'm not done with you yet. Let's do a little role reversal, as my therapist would normally say. Let's pretend that I'm the controller and that Andreas is the view. What really happens is, if something changes in some model object somewhere, Then I would send a notification to Andreas. And what this really highlights is not only that the notification is really lightweight, But since it's so lightweight, could you go get that? We can send a lot.
Really fast. So this is how you should think about Cocoa Binding. Well, maybe not. But you get the idea. Hopefully that clears up exactly what's going on when you bind objects, bind a view to another object. And with that, I think... Now, quick recap. Yeah, yeah, there we go.
So, Model-View-Controller. Again, Model-View-Controller. That's what we're based on. Cocoa lives and breathes Model-View-Controller. Because of the code reuse, that's really what we get out of Model-View-Controller here. And with Cocoa Bindings, now we get a set of reusable controller objects, as well as the notion of bindings, so that we can cut out all the glue code that we're used to writing.
And to keep with the outline that I introduced, some of the new features we've added to Cocoa Bindings includes a tree controller. So now you have something to bind to from the outline view in the browser. We've also added filtering directly to the array controller, also to NSArray and NSMutableArray. We've even added core data support to the controllers so that you can fetch directly in the controllers. And then we've added some new bindings and bindings options, which you'll see in the documentation forthcoming. One more.
And so the tips and tricks for rolling your own bindings come down to making sure that your model objects and your view objects are key-value coding, key-value observing, and key-value binding friendly. This means making sure that all of your model objects have actual KVC accessors, and that you can do the key-value observing notifications.