Code Quality: Factory Methods – View Controllers

With the introduction of storyboards and segues, it became less necessary to create new view controllers in code, but every now and then, you still need code to create a new view controller. You can download the code for this article at https://github.com/Five-Pack-Creative/ArticleFactoryMethods

Attempt #1

Our sample app for this article has two view controllers, HomeViewController wants to display DetailViewController. Easy peasy, just do something like this:

1

2

3

4

5

6

– (IBAction)showDetailTapped:(id)sender {

DetailViewController *detailController = [[UIStoryboard storyboardWithName:@”Main” bundle:nil] instantiateViewControllerWithIdentifier:@”DetailViewController”];

[self presentViewController:detailController animated:YES completion:nil];

}

Ok, that will work, but there’s a problem. Now HomeViewController has to know the name of the storyboard and the identifier used to create DetailViewController. With this simple example, it’s not much of a problem, but if multiple places in code are using DetailViewController, they all have to know this information and use the correct strings. Let’s make it better.

Attempt #2

We should put the call to instantiateViewControllerWithIdentifier inside DetailViewController and then expose that so callers can create a new instance easily. How about this, inside DetailViewController, add an init method:

1

2

3

4

5

6

7

– (instancetype)init {

// Spoiler alert: don’t do this!

self = [[UIStoryboard storyboardWithName:@”Main” bundle:nil] instantiateViewControllerWithIdentifier:@”DetailViewController”];

return self;

}

And then the HomeViewController code changes to this:

1

2

3

4

5

6

– (IBAction)showDetailTapped:(id)sender {

DetailViewController *detailController = [[DetailViewController alloc] init];

[self presentViewController:detailController animated:YES completion:nil];

}

Much better! Now any class that wants to instantiate a new DetailViewController doesn’t have to know anything about what storyboard, or even that there is a storyboard. You don’t have to remember the identifier or make sure you type it correctly. So, we’re done, right? No, there’s a problem lurking…

The Problem

In order to see the problem, let’s put a breakpoint in DetailViewController at the top of our new init method and run.

You can see, even before the first line of the init method has run, self has a value. Ok, that makes sense, the call to alloc reserved a memory location for the new variable, but now let’s step over to the next line and look at self again.

Woah! The value is different. Well, that makes sense too because the call to storyboardWithName is going to create and return its own value. What this means is that an instance of DetailViewController will get created with alloc and then immediately thrown away (and dealloc’ed) in the first line of the init method.

Final Attempt

So the principle of moving the call to instantiateViewControllerWithIdentifier inside DetailViewController was right, we just picked the wrong way to do it. Rather than overriding the init method, we want a factory method to create and return an instance.

1

2

3

4

5

+ (instancetype)controller {

return [[UIStoryboard storyboardWithName:@”Main” bundle:nil] instantiateViewControllerWithIdentifier:@”DetailViewController”];

}

We expose that method in the header and then use it in HomeViewController:

1

2

3

4

5

6

– (IBAction)showDetailTapped:(id)sender {

DetailViewController *detailController = [DetailViewController controller];

[self presentViewController:detailController animated:YES completion:nil];

}

Voila! The code that knows how to create a DetailViewController is now encapsulated inside that class, callers can easily create a new instance, and our problem of creating extraneous instances is solved. It’s a thing of beauty.

Stay Connected

More Updates