views:

300

answers:

2

I am a little confused by this snippet of code (presented in the CocoaFundamentals guide) that overrides some of the methods when creating a singleton instance.

static id sharedReactor = nil;

+(id)sharedInstance {
    if(sharedReactor == nil) sharedReactor = [[super allocWithZone:NULL] init];
    return sharedReactor;
}

.

+(id)allocWithZone:(NSZone *)zone {
    return[[self sharedInstance] retain];
}

-(id)retain {
    return self;
}

In the code where the singleton instance is created the +sharedInstance method calls [super allocWithZone:NILL] from the superclass (which in my case is NSObject) The allocWithZone above is only called if you attempt to use it to create a new singleton.

The bit I am confused about is the use of retain, especially seeing as retain is also overridden to return self. Can anyone explain this, could it not be written:

+(id)allocWithZone:(NSZone *)zone {
    return [self sharedInstance];
}

-(id)retain {
    return self;
}

EDIT_001:

Based on comments and reading various posts on the web I have decided to go with the following (see below) I have chosen to go for a shared singleton approach where if needed I would have the option of creating a second or third instance. Also at this stage as I am only using the singleton for the model portion of MVC for a simple iPhone app I have decided to leave thread safety out. I am aware its important and as I get more familiar with iPhone programming I will likely use +initialize instead (keeping in mind the subclass issue where it can be called twice) Also I have added a dealloc, firstly to log a message should the singleton be released, but also to clean things up properly should the singleton be no longer required.

@interface SharedManager : NSObject
+(id)sharedInstance;
@end

@implementation SharedManager

static id myInstance = nil;

+(id)sharedInstance {
    if(myInstance == nil) {
        myInstance = [[self alloc] init];
    }
    return myInstance;
}

-(void)dealloc {
    NSLog(@"_deal: %@", [self class]);
    [super dealloc];
    myInstance = nil;
}
@end

In testing I found that I had a set the static variable to nil in the dealloc or it maintained its pointer to the original object. I was initially a little confused by this as I was expecting the scope of the static to be the instance, I guess its the class instead, which makes sense.

cheers gary

+3  A: 

There is a good example of different singleton methods with comments here on SO: What does your Objective-C singleton look like?

If it helps, the example has a different approach to allocWithZone: which returns nil.

Ross
+4  A: 

First, don't use this code. There is almost never a reason to do all this for a simple singleton. Apple is demonstrating a "Forced Singleton," in that it is impossible to create two of them. It is very rare to really need this. You can almost always use the "shared singleton" approach used by most of the Cocoa objects that have a singleton constructor.

Here's my preferred way of implementing shared singleton:

+ (MYManager *)sharedManager
{
    static MYManager *sharedManager = nil;
    if (sharedManager == nil)
    {
        sharedManager = [[self alloc] init];
    }
    return sharedManager;
}

That's it. No other code is required. Callers who use +sharedManager will get the shared instance. Callers who call +alloc can create unique instances if they really want to. This is how such famous "singletons" as NSNotificationCenter work. If you really want your own private notification center, there is no reason the class should forbid it. This approach has the following advantages:

  • Less code.
  • More flexible in cases where a non-shared instance is useful.
  • Most importantly: the code does what it says it does. A caller who thinks he's making a unique instance with +alloc doesn't encounter surprising "spooky action at a distance" behavior that requires him to know an internal implementation detail of the object.

If you really need a forced singleton because the object in question maps to a unique resource that cannot be shared (and it's really rare to encounter such a situation), then you still shouldn't use +alloc trickery to enforce it. This just masks a programming error of trying to create a new instance. Instead, you should catch the programming error this way:

+ (MYManager *)sharedManager
{
    static MYManager *sharedManager = nil;
    if (sharedManager == nil)
    {
        sharedManager = [[self alloc] initSharedManager];
    }
    return sharedManager;
}

- (id)init
{
    NSAssert(NO, @"Attempting to instantiate new instance. Use +sharedManager.");
    return nil;
}

// Private method. Obviously don't put this in your .h
- (id)initSharedManager
{
    self = [super init];
    ....
    return self;
}
Rob Napier
You might want to update this to discuss thread safety. Mike Ash has a good post about that along with other singleton issues: http://www.mikeash.com/pyblog/friday-qa-2009-10-02-care-and-feeding-of-singletons.html
nall
Better: readers should check out Mike Ash's post. It extremely thorough on how to extend this for thread-safety. Thread-safety issues on shared singletons only impact initial creation. It is very rare for me to encounter this kind of thread problem because of how I generally handle threading (in my code, it is extremely unlikely that the first use of a singleton would be on a worker thread). That said, I'm tempted to employ Mike's use of +initialize because it's so cheap to implement, and I think the "too early init" problem is unlikely in most situations.
Rob Napier
@Rob this still doesn't disallow multiple instances. It only "disallows" them by convention. Peter Hosey wrote a great post on how to more properly implement singletons: http://boredzo.org/blog/archives/2009-06-17/doing-it-wrong
Dave DeLong
Dave DeLong: +1 (obviously ☺), but I will point out that Rob acknowledged that and makes an opposing case.
Peter Hosey
@Dave, Peter's write-up is very good, and I recommend folks read it, but as Peter notes, I argue that overriding +allocWithZone: is bad because it injects surprise and masks bugs (as Peter argues and I agree about overriding retain/release). My last example does disallow multiple instances by more than convention. If you want to protect against the caller who ignores the warning and calls -initSharedManager anyway, add "if (self != theSharedManager)". I typically consider the warning sufficient. If they ignore the warning, bad things will happen. Don't do that. Their program won't work.
Rob Napier
Thank you for all the comments, very much appreciated as its a bit of a jungle on the web when it comes to how/how not to write a singleton. Everyone has their own angle and its not easy working out the whys and wherefores of each.
fuzzygoat