Sunday, October 17, 2010

Handling Initialization Failure

A few months ago a friend of mine asked me the following question: "What should I do if I want to return nil in one of my initializers?" Returning nil in an initializer is nothing fancy. I do it all the time. However, there are a few rules you should be aware of.
  • Only return nil (in an initializer) if you can't perform the initialization. If a caller passes your initializer a path to a file that does not exist and the object can't work without a file it would make sense to return nil.
  • Don't return nil if you can find a nice "workaround" for a problem during initialization such as replacing a missing argument with a sensible default value.
  • If you want to return nil and you haven't sent a initialization message to super yet perform the following steps:
    • cleanup any resources you may have created,
    • call [self release]; and then
    • return nil
  • If you want to return nil and already have sent a initialization message to super (which returned nil) perform the following steps:
    • cleanup any resources you may have created,
    • return nil
If you return nil you must have a good reason. It is a good idea to let the caller know what went wrong during the initialization process. In Objective-C/Foundation we can simply use NSError to pass the cause of a problem back to the caller. Imagine a class called Person. A Person has a first and a last name. One can create a Person by using -initWithFirstName:lastName:error:. If the passed first and/or last name is nil -initWithFirstName:lastName:error: creates an error object, sends release to self and returns nil.

#import "Person.h"

enum {
   PersonErrorCodeFirstLastNameNotValid = 0
};
typedef NSInteger PersonErrorCodes;

@implementation Person

@synthesize firstName, lastName;

- (id)initWithFirstName:(NSString *)initFirstName 
               lastName:(NSString *)initLastName 
                  error:(NSError **)error {
   if(initFirstName == nil || initLastName == nil) {
      if(error != NULL) {
         NSMutableDictionary *u = [NSMutableDictionary dictionary];
         [u setValue:@"first/last name not valid." 
              forKey:NSLocalizedDescriptionKey];
         PersonErrorCodes c = PersonErrorCodeFirstLastNameNotValid;
         *error = [NSErrorerrorWithDomain:@"com.example.unique" 
                                     code:c 
                                 userInfo:u];
      }
      [self release];
      return nil;
   }
   self = [super init];
   if(self != nil) {
      self.firstName = initFirstName;
      self.lastName = initLastName;
   }
   returnself;   
}

- (id)initWithFirstName:(NSString *)initFirstName 
               lastName:(NSString *)initLastName {
   return [self initWithFirstName:initFirstName 
                         lastName:lastName 
                            error:NULL];
}

- (id)init {
   return [self initWithFirstName:[NSString string
                         lastName:[NSString string]];
}

- (void)dealloc {
   self.firstName = nil;
   self.lastName = nil;
   [super dealloc];
}

@end

As you can see, this class has three initializers: -init, -initWithFirstName:lastName: and -initWithFirstName:lastName:error:. The designated initializer is -initWithFirstName:lastName:error:, which is used by a caller which is interested in the cause of a failed initialization. -initWithFirstName:lastName: can be used by a caller which only wants to create a Person and see if it worked or not.

Wednesday, October 13, 2010

Singletons to hell

In my talk about "Complex Projects with Cocoa" at a German iOS and OS X developer conference I warned the audience about the singleton design pattern. This warning resulted in something that I would call a small flame war about singletons. Although I have nothing against controversial discussions I was a bit surprised that many people tried to defend the singleton design pattern. For the rest of the conference I asked myself two simple questions:

  1. Why are so many people defending the singleton design pattern?
  2. How can these people be convinced that they are defending bullshit?

I will try to answer them both.

Why are so many people defending the singleton design pattern?

I think this is because the singleton design pattern is easy to understand. Most books and tutorials about design patterns first demonstrate the singleton pattern before anything else. So, every developer knows and understands the singleton design pattern. If this wouldn't be enough a singleton is often used by beginners to solve basic problems.

"I don't know how to pass an array from instance to instance. What do I do?"

Five minuted pass.

"Ah! LETS MAKE A SINGLETON!"

Another five minutes later and the poor developer has a singleton that holds the array of objects that can and will be access from everywhere. This makes the developer happy. He used a singleton to (superficially) solve a problem. One does not like to abandon something familiar. We all know that.

How can these people be convinced that they are defending bullshit?

Yes, there are singletons that make sense. No, I don't repeat what makes singletons so bad. So how do I convince you? Simply by quoting Erich Gamma. In an interview Erich Gamma, one of the authors of the famous book "Design Patterns: Elements of Reusable Object-Oriented Software", was asked the question "How would you refactor "Design Patterns?".

His answer:

"[...] When discussing which patterns to drop, we found that we still love them all. (Not really—I'm in favor of dropping Singleton. Its use is almost always a design smell.) [...]"

This should convince everybody.