It's a good spot. This is, in fact, a prime candidate for refactoring. Notice how similar all the chunks of code are. With the exception of the controller class, title, and image name, each chunk of code is basically identical.
The answer to whether this can be DRY'ed, yes. This can be refactored in Objective-C and probably should. We didn't do it in the book basically because Chapter 9 was already long enough without having to use Class objects or the Objective-C runtime, and we were concerned this would add something confusing to an already long and difficult chapter.
But, my blog doesn't have to be only beginner friendly, so let's look at how we could refactor this chunk of code. First and foremost, let's start by changing the controllers property from an NSArray to an NSMutableArray so its contents can be modified by an instance method.
Next, we can create a method that will add a controller to that array. Since the items that are not the same between the various chunks of code are the controller class, the title, and the image name, we need the method to take arguments for each of those.
If we know and have access at compile time to all the classes that we will be using, we can do this pretty easily by creating a method that takes a Class object. This is the object that represents the singleton meta-object that exists for every Objective-C class. When you call a class method, you are actually calling a method on this object and you can call class methods on Class objects. So, in this scenario where we know all the classes we'll be using, we can write this method:
- (void)addControllerOfClass:(Class)controllerClass usingTitle:(NSString *)title withImageNamed:(NSString *)imageName
We create an instance of the correct class by calling alloc on the Class object, which returns an instance, which we can then initialize ordinarily. We declare this an instance to be the abstract superclass of all the second level controllers, SecondLevelViewController, which allows us to use both the title and rowImage properties without having to typecast or set the values by key.
Then, our viewDidLoad method becomes much, much shorter and without all the repeated code:
But, what if you don't know all the classes at compile time? Say, if you want to create a generic class to go into a static library? You can still do it, but you lose the compile-time check for the class and have to use an Objective-C runtime method to derive a Class object from the name of the class. Easy enough, though. Under that scenario, here's our new method:
- (void)addControllerOfName:(NSString *)controllerClassName usingTitle:(NSString *)title withImageNamed:(NSString *)imageName
Notice that the only difference is that we take an NSString * parameter rather than a Class parameter, and then we get the correct Class object using the Objective-C runtime function called objc_getClass(). This function actually takes a C-string, not an NSString, so we get a C-string using the UTF8String instance method on our NSString.
In this case, we have to change our viewDidLoad method slightly to pass string constants, rather than Class objects:
Either of these options will be much easier to maintain and extend than the version in the book. You should be on the lookout for refactoring opportunities in your own code, as well. Sometimes an ounce of refactoring can save a pound of headache down the line.