Actions are a common way of sending events around in Cocoa. They’re very straightforward; they deal with the simplest kind of event, such as the user clicking on a button or menu item.
Actions consist of two bits of information bundled together: A selector, and a pointer to an object (the “target”). When the event occurs, a message with that selector is sent to the target object, passing the event’s sender as the sole parameter.
(If you’ve come from a Windows programming background, remember: ‘message’ in this context has the Smalltalk meaning, of dispatching a method call to an object that may or may not implement it – it has nothing to do with event queues, message pumps, etc)
User-interface widgets in Cocoa generally have one Action built-in, to handle their default behaviour, and you can set it with code or with Interface Builder. Some have additional Actions that you can only set in code (for instance, Table Views have an additional Action that is triggered when you double-click on an item).
To set Actions in Interface Builder 3.0, right-click on the widget in question; a dark gray “HUD” window pops up with a bunch of “connectable” items listed. Find the one labelled “selector”, and click-and-drag on the circle next to it. This should drag a line out – I think of it as pulling a retractable power cable out of its recess. Drag the cable to the target object and let go. Another HUD window pops up, listing the methods the object has that’re compatible with Actions. Click on one to plug it in, and you’re done.
So if you have a “Take Photo” button, and you set its built-in Action’s target to an instance of MyApplication, and the Action’s selector is “takePhoto:”, then clicking the button calls MyApplication’s takePhoto method, passing the button as a parameter. That’s it! It’s just a really obvious callback!
At least, in the simplest form. In Cocoaland, it can be slightly more complex than this, thus the reason for this article :)
See, if an Action’s target is left null, Cocoa tries to figure out what the object should be automagically. This isn’t just to help out lazy programmers: sometimes the proper target isn’t known until the moment the user clicks the button or menu item or whatever.
Cocoa does this by walking through a list it maintains – the ‘responder chain’ – looking for an object that responds to the message named in the Action’s selector. The exact details of what is, and isn’t, in the responder chain are fiddly and documented by Apple here, but the important points are that the currently-selected interface widget is the first item in the list, followed by it’s parent, and so on up the view hierarchy, until you hit the window, after which “special” objects get a look-in (delegates, the current document controller, and the application itself – but you probably don’t care about that stuff just yet).
For menu items, Cocoa actually checks the chain each time the menu is displayed, not just when you click on it. It grays out items with selectors that no-one responds to. A huge amount of potential grunt-work thus vanishes in a puff of loosely-coupled logic: Actions like “Save” and “Print” get grayed out if no documents are open, because typically only document controllers respond to them, and there will be none in the chain. If multiple documents are open, the correct one will get saved or printed. Similarly for all of the “bonus” functionality Cocoa provides, like spellchecking as you type, which follows you to the currently-selected text box, or cut-and-paste, or whatever.
As well as setting the object pointer to null in code, you can do this in Interface Builder too, by connecting Actions to the ‘First Responder’ proxy object. You can’t miss it, it’s usually the 2nd or 3rd object in the IB document. New projects generally have all the main menu items hooked up to the First Responder already.
You can add your own actions to the First Responder in IB (remember, it’s not a real object, just a placeholder for ‘stick a null pointer into the target field’), and if you write a method that matches, in one of your own classes, which somehow ends up in the responder chain, it’ll Just Work.
These are super-simple. They’re just member variables of one object, which point to another object, and which Interface Builder has been told about (in XCode 3.0, this happens automagically; in earlier versions you add them by hand). Once IB knows about them, you can just hook them up using the right-click Connections window, just like Actions, and at runtime the pointers will have been set correctly. This is the main way your application code will ‘find’ widgets.
The care and feeding of Interface Builder documents will be dealt with in more detail, in future articles in this series…