Cocoa
So, you’ve read the previous four cocoa articles and you have a bunch of answers for Trivial Pursuit: Cocoa Edition. But actually getting going with an application can be daunting.
In part this was, for me, because Cocoa idioms are quite different from those of frameworks I’ve used before. Cocoa has quite a lot of time-saving “magic”, but it’s not always obvious what and where that’s going on; “where I come from,” the infrastructure is more overt (but less useful). In most of those, the first order of the day was to create a new subclass of Application and start filling in the blanks.
In Cocoa, many of the framework classes are designed to be used off-the-shelf without subclassing, including the Application class (NSApplication – all Cocoa classes start with NS, but I may omit the NS when referring to classes casually). You can subclass most of them if you need to change some behaviour, but this tends to be for rarer special cases, not the main jumping-off point for writing your app. Also, a lot more of the setup work is done using tools, inside Interface Builder, rather than code.
So, where do you start?
Let’s say you fire up XCode, and start a new project. You’ll be offered a huge range of templates to get going. Stick with a basic Cocoa Application (or Cocoa-Python Application if you’re working in Python). Ignore all the Core Data Document Based Plug In Nuclear Fusion Mousetrap Inversion Field Application templates for now: let’s limit how much of the API we need to learn at once.
What you’re likely to do first, is open up MainMenu.xib and start laying out the main window. This is easy, and gives you something visual fast, but then you need to start hooking it up to real code.
Let’s look at the files lying around your project. I like Python and don’t feel like teaching Objective-C right now, so I’m going to go through the Python Cocoa Application template (the Obj-C one is very similar, though):
main.m
main.py
YourAppNameHere_AppDelegate.py
Info.plist
YourAppNameHere_Prefix.pch
MainMenu.xib
InfoPlist.strings
(Files with YourAppNameHere change their name to match the one you gave when you created the project.)
First off, these two files you can largely ignore:
main.m: Searches for main.py, and runs it. It also displays any uncaught Python exceptions and exits. That is all. It has a .m file extension because it’s Objective-C. Even in pure Objective-C projects, this file does very little.
YourAppNameHere_Prefix.pch is a “precompiled headers” file. Since there’s only one Obj-C file and the rest is Python we can essentially ignore this.
These, you’ll be working with occasionally:
Info.plist is a Property List (an XML file of key-value pairs – Apple use them throughout OS X) that configures your application. Just to be clear, these are not instructions for XCode (it simply copies the file into the application, along with the other resources, when you compile). Info.plist is read by Cocoa, and the OS itself at various times when it needs to know about the app.
When it first sees the app, OS X looks in Info.plist to find out about the app’s title and icon. OS X looks in Info.plist to discover what file-types it can load. Some of the data is also used by Cocoa after your app launches (in particular to configure Document-based apps’ open/save dialogs). These items, along with copyright notices, version numbers, and so on, make up the bulk of Info.plist.
Typically you’ll set all this up when you first start out, and only make occasional changes as you add support for new file-types.
main.py: Your Python entry point. This is where it all kicks off. It’s also pretty barren: All the work is being done behind your back, inside frameworks. Once it launches the application object (last line of the script), execution doesn’t return until you shut down the app.
However, you will need to edit this file from time to time: Any custom classes you create, and use in Nib files, you must load here, so Cocoa can find them when it gets round to loading the Nibs. This just means you need to import the modules that contain them; PyObjC does the rest (this is a PyObjC-specific quirk: in pure Objective-C, it’s taken care of by the language runtime).
If you forget, Cocoa will fall back to an ancestor class (eg NSView instead of MyFancyView) and spit a warning out to the console.
This, you may have already jumped ahead and started fiddling with:
MainMenu.xib: Contains your entire user interface, at least for simple projects. Document-based apps break out per-document UI into a separate file, and other apps may break down the UI even further. For now though, this is it. Its loading is caused by an entry in Info.plist referring to it.
This is the first of the files you’re likely to work with regularly:
YourAppNameHere_AppDelegate.py: Cocoa uses delegates a lot – and in this file is your first. Since subclassing isn’t the primary means of customisation in Cocoa, many Cocoa classes take a delegate to customise their behaviour. The application is no exception.
The App Delegate is responsible for answering questions like, “When the last window closes, should the application quit?” or “Does the user want to save their work before quitting?” It also receives messages (remember: method calls, not posts to the event-loop) when the application finishes loading, gains/loses focus, is hidden/unhidden and so forth. It’s also responsible for tasks such as opening files.
Delegates, like most objects in Cocoa, are typically created and connected-up by the Nib. So the app delegate class from the template contains very little code; just enough to declare the class, but nothing that does any instantiation, no singleton infrastructure or anything like that.
Because Objective-C messages (as we discussed before) don’t rely on C++-style virtual methods, the delegate need not even inherit from any particular class or interface. In our Cocoa-Python Application template, it just inherits from the base class, NSObject. This also means one object can be the delegate for several objects, even of different types; although, taken to extremes, this can lead to code which is confusing to read.
To find the instance itself, you’ll need to go back and have a look in MainMenu.xib – you’ll find one sitting around, looking like a big blue cube.
You’ll also find a proxy object representing the application; it should have a delegate Outlet that is already pointing to your delegate instance. You can see this if you right-click on it.
So to sum up the startup process:
Cocoa creates an NSApplication internally
main.py imports any necessary modules before the .xib(s) are loaded
It then launches the application
Which loads MainMenu.xib (because it’s listed in Info.plist)
Which contains the App Delegate, and points the Application to it
You can create whatever other objects in the MainMenu.xib you feel necessary
You customise the App Delegate to your needs, including adding Outlets which can be hooked up to those other objects, so the App Delegate code can get at them. Your application’s main window will be one of them (in a non-Document application).
Now, if you wish, you can write a lot of your application’s “guts” in the delegate: Make a button in IB, write an Action to handle it in your delegate, connect them together inside IB, and it should all Just Work. Use threads or NSTimers to do background processing. Fill in methods for delegate callbacks. That’s an application right there.
For anything but the most trivial example, though, I recommend reserving the App Delegate just for handling its “official duties”, and creating your own unique Controller to run the application itself. More on this in a moment.
I’m not going to get into this in-depth, but it’s probably worth mentioning localisation here. Inside the project you’ll find English.lproj, a Language Project. Each localisation your application contains, has its own .lproj. It’s really just a folder, though; any localised resources are placed inside it, and Cocoa ensures they are loaded from the correct .lproj for the user’s preferences.
Nibs and string tables are the file-types most often found here. One thing is slightly confusing: In XCode, localised files are lifted out of the lproj file and displayed separately. For instance, MainMenu.xib and InfoPlist.strings are both inside English.lproj if you look in the folders on disk, but XCode shows them at the top level alongside everything else, and each language its localised into is listed as a child of the resource, not the other way round as on disk.
Incidentally, InfoPlist.strings just localises the human-readable strings from Info.plist so that your product name, copyright notice, etc get localised. Although not everyone thinks this is a good idea.
There are a lot of these in Cocoa. You’ll make some yourself, and use others off-the-shelf (those will get an article of their own).
You’ll typically make one to host the guts of the application (the “business logic” as Java Enterprise people like to say). Just create a new .py file:
from Foundation import *
from AppKit import *
class YourAppNameHereController(NSObject):
@IBAction
def myFirstAction_(self, sender):
# Do stuff here
pass
Save it, and if you’re working in a third-party editor, make sure it’s added to the XCode project in the Classes section (just drag-and-drop it in). You don’t need to build the project just yet, just go straight to IB, and you should be able to now make an instance of YourAppNameHereController. It won’t do anything yet, but you can start filling the Python class with the code you need, expose it with the @IBAction decorator, add Outlets by creating member variables (eg mainWindow = IBOutlet()), and then hook it all up in IB. Now import YourAppNameHereController in main.py and build your project: It should all Just Work(tm).
You may be tempted to get busy writing initialisation code in the constructor. This way, pain lies. This is because you cannot guarantee which order the objects in your Nib are created. Don’t go there. Instead, there are two good ways to handle init, depending on your exact requirements:
awakeFromNib. This is called on all objects in a Nib file once the entire Nib has been loaded, and all of the connections (outlets etc) have been made. This happens every time the Nib loads. If you have multiple Nibs, the others might not have been loaded yet. You still can’t guarantee the order in which awakeFromNib is called.
applicationDidFinishLoading. This is called on the Application Delegate at startup, once loaded, but before any event messages are sent. It never gets called again.
As you may be getting used to by now, Document-based applications do this slightly differently. I’ll be doing a separate article on those some day. Hopefully you have enough to find your way around now, at least with some basic controls.