Cocoa, Core Data, and me (IV)

I’m back after finally getting the tax forms mailed in. What a waste of time that is. A civilized society shouldn’t be doing these things to their citizens.

Now the time has arrived to construct the CountriesController class. This class controls the model and view for the countries data element. It is instantiated by the AppController and in turn loads and shows the Countries.nib. There’s only one single instance of the CountriesController class, so it’s a singleton.

Actually, I can’t see any reason why there couldn’t be multiple instances, and I’m sure I’ll have a use for that later. It’s hard, though not impossible, to imagine having to use multiple instances of a country browser on screen. Stretching things a bit, imagine if you had a relationship like “occupied by other country”, which would necessitate one country to refer to another. In that case, there would be two country browsers on screen at the same time, each owned by a separate instance of the CountriesController class.

For a more realistic example, at least for an accounting app, consider general accounts relating to other general accounts, or a customer that is the main customer, refering to a collection of subsidiary “customers”, which are actually departments. But we’ll get to this later. For now, we’re concerned with designing and creating the CountriesController class only.

To create it, go “File… New File…”, then select “Objective-C NSWindowController subclass” in the assistant:

New file, NSWindowController derived

The CountriesController will be the actual controller of the visible view on the screen, so that’s why it needs to be an NSWindowController.

Click “Next”, and enter the name “CountriesController”. Check that the file is added to the right project, then click “Finish”.

Doubleclick the “Countries.nib” file in XCode to open up Interface Builder. Now we need to add in our new CountriesController class as its “File Owner”, but to do that, we first have to make Interface Builder realize there now exists a thing called “CountriesController”. That is done by dropping the CountriesController.h file on the Countries.nib panel. Drag the CountriesController.h file from XCode and drop it on the Countries.nib pane:

Dropping CountriesController.h on Countries.nib

If you did this right, the Countries.nib pane will now show the CountriesController class in the Classes tab:

CountriesController class in Classes tab

Now switch back to the “Instances” tab in the Countries.nib pane (you won’t see a trace of the CountriesController class here, but rest assured, Interface Builder hasn’t forgotten it). Select the “File’s Owner” object (it’s not a real object, actually), then open the inspector (shift-command-I). Select “Custom Class” in the inspector’s dropdown, then select “CountriesController” from the class list:

CustomClass selection.

If you can’t find “CountriesController” in the class list, there is something wrong with your drag-and-drop skills, so go back and do the preceding steps over again.

The above steps wired up the CountriesController class to the windowing system, but there’s another thing that needs wiring up, namely the “managed object context”. There’s just one such context in the application, and the core data related code in the countries-related classes need to be able to find it. The natural way of finding such global objects is through the parent graph. In other words, we don’t want objects hidden deep in an application go ask for global objects directly. No, we want them to ask their owners or parents for them, since that makes for looser coupling and more cohesiveness. In regular language: it makes for less crap code.

To see what we need, select the CountriesAC array controller in the Countries.nib panel, and open the inspector pane if it isn’t already open (shift-command-I). Select “Bindings” in the dropdown, open up the detail pane under “managedObjectContext”, select “File’s Owner (CountriesController)” in the “Bind to:” dropdown, and finally enter “managedObjectContext” in the “Model Key Path” field. After all this, the inspector should look like this:

NSArrayController inspector

What this tells the array controller is, that if it needs the managed object context (and boy, it sure does), it should ask the file’s owner, which is a CountriesController, using the method managedObjectContext (which we haven’t coded yet, but are going to code in the next step).

Coding the CountriesController class

The CountriesController.h file initially contains no instance variables or methods. We need to add a reference to a managedObjectContext, so that we can return it when the core data components in the Countries.nib file ask for it. We could, of course, ask the application object for the managed object context every time we need to supply it, but it seems so inefficient, so I prefer to keep it after asking for it once. After adding in the reference, the header file looks like this:

CountriesController header file.

In the implementation file, we’ll add in a couple of things. First, we need to override the init method, so we can tell the window controller (which is what our superclass is) which nib file to load. Second, I added in a logging statement in the windowDidLoad method, but that’s just to have something to look at during testing and debugging. Thirdly, I added the managedObjectContext method, so the core data components can call it. Note that you don’t need to declare methods in header files in Cocoa, even though it’s a pretty good idea to do so. I hope I’ll be more disciplined in the future. The result:

CountriesController.m file.

Note the way one gets the managed object context by asking the NSApp delegate for it. This is how it’s done in a “Core Data Application”. If you create a core data documentbased application, it’s different. Then each document object contains a managed object context instead. But that’s another story.

If you remember, the AppController calls showWindow on the CountriesController. Since our CountriesController is derived from an NSWindowController, that works just fine without us having to implement anything.