Metaprogramming in Objective-C
Posted by Paul Pilone on Apr 18, 2011 in iOS Development | 2 commentsFor the past few months I’ve gotten the opportunity to develop in Ruby and experience first-hand the awesomeness that is Ruby on Rails. I’ve been keeping up with my normal iOS development and would be lying if I said RoR development hasn’t changed my Objective-C habits. Ruby/Rails development has drilled into my head ideas such as DRY (Don’t Repeat Yourself), CoC (Convention over Configuration), and the idea of meta programming. I’ve started to realize that Objective-C and the iOS SDK leads you into the direction of hard-coded repetition: I cringe every time I implement UITableViewDataSource methods ::I’m looking at you UITableViewCell:: and I’m tired of implementing the same logic in navigation-based apps.
Objective-C has some built in support for meta-programming with calls such as doesRespondToSelect:, valueForKey:, and makeObjectsPerformSelector:. I’ve recently come across some nice solutions for UITableViewDataSource methods in iOS Recipes: Tips and Tricks for Awesome iPhone and iPad Apps by Paul Warren and Matt Drance.
I decided I’d see how far I can push the idea of meta-programming in Objective-C and came up with a fun solution for the very common navigation pattern: List of Models View -> Details of Model View. The solution relies heavily on CoC and meta programming.
The solution makes use of a custom class, ModelListViewController, which implements three methods:
- (void)insertNewObject; - (NSString *)modelNameForList; - (void)presentViewControllerForObject:(NSManagedObject *)anObject newObject:(BOOL)isNew;
The implementation of each method is fairly short:
- (void)insertNewObject {
// This example uses Core Data, but you could replace this code with any initialization code.
NSManagedObject *anObject = [NSEntityDescription insertNewObjectForEntityForName:[self modelNameForList] inManagedObjectContext:_managedObjectContext];
[self presentViewControllerForObject:anObject newObject:YES];
}
- (NSString *)modelNameForList {
NSString *className = NSStringFromClass([self class]);
NSRange suffixRange = [className rangeOfString:@"ListViewController"];
NSString *modelName = nil;
if (suffixRange.location != NSNotFound) {
modelName = [className substringToIndex:suffixRange.location];
}
return modelName;
}
- (void)presentViewControllerForObject:(NSManagedObject *)anObject newObject:(BOOL)isNew {
// Dynamically initialize the correct detail view controller for this model by appending "ViewController" to the end of the model name.
NSString *viewControllerName = [NSString stringWithFormat:@"%@ViewController", [self modelNameForList]];
// NSClassFromString() allows us to perform this dynamic initialization.
ModelViewController *managedObjectController = [[NSClassFromString(viewControllerName) alloc] init];
managedObjectController.managedObject = anObject;
managedObjectController.newObject = isNew;
// Wrap in a navigation controller.
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:managedObjectController];
if (isNew) {
[self presentModalViewController:navController animated:YES];
} else {
[self.navigationController pushViewController:managedObjectController animated:YES];
}
// Clean up.
[navController release];
[managedObjectController release];
}
The method that makes magic happen, modelNameForList, looks at the class name and pulls out anything before “ListViewController”. This allows you to dynamically insert new Model objects (assuming Core Data usage – but would work for anything) or dynamically initialize and present detail view controllers. The presentViewControllerForObject:newObject: method expects detail view controllers to be named something like ModelViewController, where “Model” is replaced with the actual model name – Convention over Configuration FTW!
Using an example: Let’s imagine you have a model named Car and have added a subclass of ModelListViewController named CarListViewController, and a class called CarViewController. By simply subclassing ModelListViewController, you’ve already achieved normal navigation based behavior for adding a new Car or tapping to view details of an existing Car with 0 lines of code in any of the Car classes. Of course, this example assumes you’ve implemented some of UITableViewDelegate and DataSource methods in ModelListViewController, but those examples are for another post.

I’ve been diving in to the trenches again with my iOS department at Twin Engine Labs, and have been going through a project with over 250 classes for displaying remote and locally cached data. Needless to say, the project is massive (over 160 screens were designed by the design department O.O).
I’ve been feeling massive pain after coming from a primarily Rails background, pretty much exactly as you’ve posted here.
I’m going to encourage the team to test this out on our next iOS app we start from scratch. Great stuff! Exactly what I was looking for
This is an awesome idea, and thanks so much for this. I’m using this to create metaprogrammed templates for each of the CRUD actions stemming from Core Data. For instance, you can create a controller called “UserListViewController”, and the app will extract “User” from the class name, extract all of the relevant records from Core Data, and show the records.
Really helpful post, and thanks again!