iPhone Programming: Hard Mode

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.

Getting Started

By default, an iPhone application template gives you a main.m file, but it’s pretty basic:

[sourcecode language=”objc” firstline=”9″]#import <UIKit/UIKit.h>

int main(int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}[/sourcecode]

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 or 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 NSStrings (@"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:

[sourcecode language=”objc” firstline=”16″]#define JK_EasyCFString(X)
(CFStringCreateWithCString(kCFAllocatorDefault, (X), kCFStringEncodingASCII))[/sourcecode]

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 “HardModeAppDelegate.”

[sourcecode language=”objc” firstline=”41″ highlight=”42″] // Create a class to serve as our application delegate.
Class appDelegate = objc_allocateClassPair(NSClassFromString((id)NSObjectString), "HardModeAppDelegate", 0);[/sourcecode]

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:

[sourcecode language=”objc” firstline=”44″ highlight=”46,49,50,51,52,65″] // Conform to the UIApplciationDelegate protocol.
Protocol *appDelegateProto = NSProtocolFromString((id)UIApplicationDelegateString);
class_addProtocol(appDelegate, appDelegateProto);

// Add methods.
SEL applicationDidFinishLaunchingWithOptions = NSSelectorFromString((id)appDidFinishLaunchingOptionsString);
class_addMethod(appDelegate,
applicationDidFinishLaunchingWithOptions,
(IMP)UIApplicationDelegate_ApplicationDidFinishLaunchingWithOptions,
"v@:@@");

SEL dealloc = NSSelectorFromString((id)deallocString);
class_addMethod(appDelegate,
dealloc,
(IMP)appDelegate_dealloc,
"v@:");

// Add an instance variable for the window.
class_addIvar(appDelegate,
"window",
sizeof(id),
log2(sizeof(id)),
"@");

// Now that we’ve added ivars, we can register the class.
objc_registerClassPair(appDelegate);[/sourcecode]
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 self and _cmd. For instance, to call -bounds on a UIScreen object, I had to cast the return type of objc_msgSend to CGRect:
[sourcecode language=”objc” firstline=”134″ highlight=”135″]CGRect (*msgSendBounds)(id self, SEL _cmd);
msgSendBounds = (CGRect(*)(id, SEL))objc_msgSend_stret;[/sourcecode]
Similarly, when calling -initWithFrame: on a UIWindow or UILabel, I casted it to take in a CGRect argument:
[sourcecode language=”objc” firstline=”141″ highlight=”142″]id (*msgSendCGRect)(id self, SEL _cmd, CGRect rect);
msgSendCGRect = (id(*)(id, SEL, CGRect))objc_msgSend;[/sourcecode]

Accessing Instance Variables

In my implementation of the HardModeAppDelegate class’s -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:
[sourcecode language=”objc” firstline=”196″]Ivar windowIvar = class_getInstanceVariable(self->isa, "window");
id window = object_getIvar(self, windowIvar);[/sourcecode]

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.

Autorelease is Not Your Friend

How many times have you written this line?

NSMutableArray *foo = [[[NSMutableArray alloc] init] autorelease];

At first glance, it looks fine. foo is an autoreleased NSMutableArray that you can use and, at the end of the method, it’s gone into the ether of the autorelease pool. Don’t get me wrong, most of the time, this use of -autorelease is acceptable. But, in this post, I’ll try to convince you to use autorelease differently in subtle ways. Continue reading Autorelease is Not Your Friend

Quick Tip: Don’t Do This

I could not find out where a bug was coming from for the life of me today. Naturally it one of those “assignment instead of equality” bugs that seem to crop up when trying to code too quickly. The difference here was that I had implemented a subclass of NSObject that reimplemented the method +resolveInstanceMethod:. So, the code went like this:

+ ( BOOL )resolveInstanceMethod:( SEL )sel
{
    if ( sel = @selector( setFoobar: )) {
        return class_addMethod([ self class ], sel, ( IMP )setFB, "v@:@" );
    } else if ( sel = @selector( foobar )) {
        return class_addMethod([ self class ], sel, ( IMP )getFB, "@@:" );
    } else {
        return [ super resolveInstanceMethod:sel ];
    }
}


For those of you keeping score at home, when the Objective-C runtime tried to resolve any selector, this method happily added a selector for -setFoobar: to self’s class and returned YES.

Don’t do this.

New iPhone Project: uWarranty

So, I created a new iPhone application called uWarranty. It used an unpublished Apple API for warranty status (from selfsolve.apple.com), and so I got this after submitting it:

Thank you for submitting your application to the App Store. Unfortunately, your application, uWarranty, cannot be added to the App Store because it violates section 3.3.7 of the iPhone Developer Program License Agreement:

“Applications may not perform any functions or link to any content or use any robot, spider, site search or other retrieval application or device to scrape, retrieve or index services provided by Apple or its licensors, or to collect, disseminate or use information about users for any unauthorized purpose.”

OK, I get it. That’s fine and is Apple’s prerogative. But now I have this program and all the development time that went into it. So what do I do? I guess the answer is to open-source it, just like AppSales Mobile. Watch this space for more details as I clean up the code and throw it up on a public repository somewhere.

Cocoa Touch Tutorial: Stripping Non-Alphanumeric Characters on Entry in a UITextField

In a previous post, I showed you how to trim non-alphanumeric characters from a string. Here I’ll go more in-depth and show a method that I wrote to restrict text entry in a UITextField to alphanumeric characters. Since I also wanted the characters to be uppercase, I’ll also ensure that only uppercase characters are allowed.

This should all happen in the - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string method of your UITextField’s delegate (which, of course, must implement the UITextFieldDelegate protocol). I’ve implemented it as follows:

- ( BOOL )textField:( UITextField * )textField
shouldChangeCharactersInRange:( NSRange )range
  replacementString:( NSString * )string
{
    /*
     * We only want uppercase letters and numbers in this text field, so if
     * this method is adding something else, we don't want it. But we also
     * want to support copy-and-paste, so it's not always going to be one
     * character added.
     */
    BOOL shouldAllowChange = YES;

The shouldAllowChange variable is set to YES initially because we want to allow this change when possible. The method will test the string to see if it meets criteria for rejection as we move forward.

    NSMutableString *newReplacement =
    [[ NSMutableString alloc ] initWithString:[ string uppercaseString ]];
    
    if ( ! [ string isEqualToString:newReplacement ]) {
        shouldAllowChange = NO;
    }

First, we define newReplacement. It’s an NSMutableString so that if we discover non-alphanumeric characters in it, we can remove them on-the-fly. It also serves as a convenient string against which we can test to see if string is already uppercase.

    NSCharacterSet *desiredCharacters =
    [ NSCharacterSet alphanumericCharacterSet ];
    
    for ( NSUInteger i = 0; i < [ newReplacement length ]; i++ ) {
        unichar currentCharacter = [ newReplacement characterAtIndex:i ];
        
        if ( ! [ desiredCharacters characterIsMember:currentCharacter ]) {
            shouldAllowChange = NO;
            [ newReplacement deleteCharactersInRange:NSMakeRange( i, 1 )];
            i--;
        }
    }

In this section, we define the NSCharacterSet that we want to work with - in this case, the alphanumeric character set. We go through one character by a time and if the current character isn’t alphanumeric, we remove it from the NSMutableString (decrementing i so that we don’t inadvertently skip a character) and set our shouldAllowChange flag accordingly.

    if ( shouldAllowChange ) {
        [ newReplacement release ];
        return YES;
    } else {
        [ textField setText:[[ textField text ]
                             stringByReplacingCharactersInRange:range
                             withString:newReplacement ]];
        [ newReplacement release ];
        return NO;
    }
}

To finish, if shouldAllowChange is still true, we return YES and allow the replacement characters to be added. Otherwise, we return NO, but not before using our replacement replacement string (say that ten times fast) to manually edit the text field’s text. The end result is a text field that will consist only of uppercase letters and numbers.