After a conversation with some co-workers, I discussed how it would be technically possible to write an iPhone app using only a main.m file—no separate class files. This post is the result of that. It’s definitely doable, but not something I’d ever recommend for shipping code.
The code is explained below, but you can get the full source in my public GitHub repository.
By default, an iPhone application template gives you a main.m file, but it’s pretty basic:
int main(int argc, char *argv)
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
To go from that code to running your application, a few things happen, mostly in
UIApplicationMain. Its last argument, typically
nil, is a pointer to an
NSString that specifies the class name of the application delegate. Normally, your Info.plist file specifies a nib file (the keys
NSMainNibFile~ipad), which in turn specifies the application delegate, instantiates it, etc.
So, first, delete the MainWindow.xib file, then all other class files (MyCoolAppDelegate.h and MyCoolAppDelegate.m, for instance). There should be three files left in the iPhone application: your Prefix.pch file (which we could technically do without), main.m, and your Info.plist file.
The Fun Begins
Administrivia: Creating Strings
Since, as a part of the exercise, we don’t want to use Objective-C, I also decided against using any constant
@"Hello, World!" and the like). To create a string, then, we’ll use
CFStrings. I created a macro to create one from a C string using ASCII encoding:
(CFStringCreateWithCString(kCFAllocatorDefault, (X), kCFStringEncodingASCII))
Don’t forget to call
CFRelease() when you’re done with it, though.
Creating a Class
We need to modify the call to
UIApplicationMain to include our application delegate class if we want this app to be anything more than a blank screen. What class name can we give it, though? We’ll need to create a class, and we’ll call it “
// Create a class to serve as our application delegate.
Class appDelegate = objc_allocateClassPair(NSClassFromString((id)NSObjectString), "HardModeAppDelegate", 0);
The first argument is a toll-free bridged
CFString that specifies
NSObject as the superclass, then we pass in the name of the class as a C string and 0 for the number of extra bytes we want (which is nearly always zero). Now let’s set it up a bit:
// Conform to the UIApplciationDelegate protocol.
Protocol *appDelegateProto = NSProtocolFromString((id)UIApplicationDelegateString);
// Add methods.
SEL applicationDidFinishLaunchingWithOptions = NSSelectorFromString((id)appDidFinishLaunchingOptionsString);
SEL dealloc = NSSelectorFromString((id)deallocString);
// Add an instance variable for the window.
// Now that we’ve added ivars, we can register the class.
That code is the same as writing an
@interface block for a new class, setting up ivars, conforming to protocols, and defining instance methods, but done using the runtime calls instead. Methods are actually C functions, but with two arguments prepended to the argument list:
id self and
SEL _cmd. Those arguments are actually passed to every Objective-C method, but usually hidden.
Custom Message Sending
The rest of the application is more of this bootstrapped Objective-C in C, but there are a few wrinkles worth discussing, most notably the use of
objc_msgSend with methods that return other than
id and/or have additional arguments beyond
_cmd. For instance, to call
-bounds on a
UIScreen object, I had to cast the return type of
CGRect (*msgSendBounds)(id self, SEL _cmd);
msgSendBounds = (CGRect(*)(id, SEL))objc_msgSend_stret;
Similarly, when calling
-initWithFrame: on a
UILabel, I casted it to take in a
id (*msgSendCGRect)(id self, SEL _cmd, CGRect rect);
msgSendCGRect = (id(*)(id, SEL, CGRect))objc_msgSend;
Accessing Instance Variables
In my implementation of the
-dealloc method, I need to access the
window instance variable to send it a
-release message. Using the
Ivar type and the
object_getIvar function, it becomes easy:
Ivar windowIvar = class_getInstanceVariable(self->isa, "window");
id window = object_getIvar(self, windowIvar);
So what do we have?
To be clear, this is not writing an iPhone app without using Objective-C, per se. The runtime is still being used, messages are being sent, all that. But it is an illustration of some of the heavy lifting that the runtime does for you. I would caution against adopting this practice for real, shipping apps.