kraftydevil
kraftydevil

Reputation: 5246

Singleton for a UIViewController with a xib

Since it doesn't make sense for my app to have more than one LoginViewController, I tried to combine the singleton pattern with initWithNibName like so:

+ (instancetype)sharedInstance {
    static id sharedInstance;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedInstance = [super initWithNibName:@"LoginViewController" bundle:nil];
    });
    return sharedInstance;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    NSLog(@"ignoring initWithNibName and calling sharedInstance");

//    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
//    if (self) {
//
//    }

    return [LoginViewController sharedInstance];
}

The problem is that the line:

sharedInstance = [super initWithNibName:@"LoginViewController" bundle:nil];

Gives me this error:

No known class method for selector 'initWithNibName:bundle:'

So how could this be the case if the exact same code would normally be called in initWithNibName:bundle? I suspect it's because sharedInstance is a static method and that UIViewController doesn't have a static method for initWithNibName:bundle.

Still, I'm wondering if there is a way around it because I prefer not to have to create a LoginViewController every time I need to use it.

I'd also like to guard against calling LoginViewController initWithNibName:bundle: and getting a separate instance.

Upvotes: 0

Views: 2495

Answers (1)

Michael
Michael

Reputation: 6515

+sharedInstance is a class method, so inside it self evaluates to the class LoginViewController and super refers to the class UIViewController. -initWithNibNamed:bundle: is an instance method instead.

static LoginViewController *sharedInstance;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    if(sharedInstance) {
        // avoid creating more than one instance
        [NSException raise:@"bug" format:@"tried to create more than one instance"];
    }

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if(self) {
        // own initialization code here
    }
    return self;
}

+ (LoginViewController *)sharedInstance
{
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedInstance = [[LoginViewController alloc] initWithNibName:@"LoginViewController" bundle:nil];
    });
    return sharedInstance;
}

there is an implicit contract with the -init method family that says that alloc+init should always return a new object or one that is semantically indistinguishable from a new object. E.g. [NSNumber numberWithInt:2] doesn't necessarily have to return always a new object, because there is no need to distinguish between two different 2's.

Therefore, you have two options: 1) raise an exception if someone tries to create an additional LoginViewController or 2) allow creating additional LoginViewControllers. The third option ( 3) just return the sharedInstance if someone tries to create a new LoginViewController ) breaks the contract of initialization, which means it will return something to the caller, that he is not expecting! If the caller is aware that LoginViewController is a singleton, he should use +sharedInstance. If the caller is not aware of it, he should either get an exception or get a new LoginViewController.

Upvotes: 4

Related Questions