Cocoa sans Xcode and Interface Builder
March 3, 2009Something that has always bugged me about Cocoa development is that it’s so tightly coupled with Xcode and Interface Builder. Virtually every single guide on the topic will use those two apps, and if you dare ask how one would go about without them, the answer will almost certainly be an exasperated “why would anyone want to do that?!”
And that’s something that bugs me even more. Good programmers love taking things apart to figure out what makes them tick, and I strive to be one. It’s not that I hate Xcode and Interface Builder, but rather that I like to understand what’s going on.
Turns out it’s not tricky at all.
Before we get started, let’s define what developing a Cocoa app without Xcode or Interface Builder actually means. No Xcode means we can use any editor and any build system we want. No Interface Builder means that we’ll be creating our views programmatically. Of course, these are three completely separate topics, and it’s quite possible to do just one or two of them. But let’s go the whole hog.
Objective-C, being a strict superset of C, has to obey every rule that C obeys. Hence, a truly minimal Objective-C program would be something along the lines of:
int main(int argc, char *argv[])
{
return 0;
}
Of course, this doesn’t do anything interesting, and crucially, it’s not a Cocoa app.
Now, what makes a Cocoa app? Well, if you create an empty Cocoa app project in
Xcode, main will contain a call to NSApplicationMain and nothing else. So, I
suppose we could just stick that in our main, call it done and go out to
lunch. But that would be cheating. Besides, what does NSApplicationMain do
anyway?
It actually has an entry in the API reference, but that
will not tell you much about what it does. That information is tucked away, for
some reason, in the docs for the NSApplication class.
With this knowledge, and considering we’re not going to load any nibs, we can change the code for our minimal Cocoa app into something like this:
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
[NSApplication sharedApplication];
[NSApp run];
return 0;
}
Save the above in a file called kakao.m and compile it with gcc like so:
$ gcc -framework Cocoa -o Kakao kakao.m
You can even run the app if you feel like it:
$ ./Kakao
Although, as you might have noticed, that won’t do much except hang until you kill it. So let’s add something more interesting, like a window.
After a quick look in the docs for the NSWindow class, we change
the code for our app into this:
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
[NSApplication sharedApplication];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUInteger style = NSTitledWindowMask
| NSClosableWindowMask
| NSMiniaturizableWindowMask
| NSResizableWindowMask;
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,400,400)
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[window makeKeyAndOrderFront:nil];
[pool drain];
[NSApp run];
return 0;
}
Note that we create an NSAutoreleasePool before we create
the window, and drain it afterwards. This is something Cocoa will usually take
care of, but since we are not yet in an event loop, we have to do it manually.
Compile and run the program like before, and lo, a window appears!
However, this is clearly not a trüe Cocoa app yet. It doesn’t get its own icon in the dock or in the cmd-tab view, and if you try launching it from the Finder, things just get plain silly.
Of course, this is something we can fix. Applications, as well as many other kinds of bundles, are nothing but directories with special names in Mac OS X.
If you’ve ever peeked inside a .app bundle (right-click, Show Package
Contents), you know it contains a directory called Contents, which contains
among other things a directory called MacOS, which contains the actual
executable for the app.
So, without further ado:
$ mkdir -p Kakao.app/Contents/MacOS
$ mv Kakao Kakao.app/Contents/MacOS/
And presto! You have a bonafide Cocoa app, complete with icon and menu bar (which is empty, but still) that can be launched from the Finder.
Now, if you have been digging around .app bundles before, you know that there
are two more files that should go in there: Info.plist and
PkgInfo. And yes, if you’re developing an actual app you plan on
having actual users for, you should create those files as well. But for our
purposes, we’ll do fine without them as long as the executable has the same name
as the bundle, minus the “.app” suffix.
Oh, and in case you’re wondering, “kakao” is Swedish for “cocoa”.
Recommended reading
- NSApplication Class Reference
- NSAutoreleasePool Class Reference
- Runtime Configuration Guidelines
- Calling Cocoa Commandline by why the lucky stiff, from which I borrowed heavily.
This is based on a presentation I gave at CocoaHeads Stockholm. If you’re into Cocoa, and live in or near Stockholm, you should drop by and say hi.