4

I'd like to have a singleton in my system but rather than have callers access it via some kind of 'sharedInstance' method, I'd like them to be able to be unaware that they are using a singleton, in other words, I'd like the callers to be able to say:

MyClass *dontKnowItsASingleton = [[MyClass alloc] init];

To accomplish this, I've tried overriding alloc as follows:

// MyClass.m

static MyClass *_sharedInstance;

+ (id)alloc {

    if (!_sharedInstance) {
        _sharedInstance = [super alloc];
    }
    return _sharedInstance;
}

My question is: is this okay? It seems to work, but I've never overridden alloc before. Also, if it's okay, could I always use this technique, rather than dispatch_once approach I have been doing? ...

+ (id)sharedInstance {

    static SnappyTV *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}
danh
  • 62,181
  • 10
  • 95
  • 136
  • 4
    It seems fine, however I'm not sure about thread safety. (Minor terminology fixup: you subclass a class, e. g. `NSObject`, when you redefine a method, that's called "overriding".) –  Dec 22 '12 at 20:57
  • alloc actually calls allocWithZone:, you should probably over ride that. – estobbart Dec 22 '12 at 21:59
  • @danh Keep in mind that this means `init` will be called repeatedly on the same object. If you're using ARC, [multiple calls to `init` on the same object is undefined behavior](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#semantics-of-init). Even if that's ok with you (other classes like `NSString` do it), make sure your `init` method handles being called repeatedly. – rob mayoff Dec 22 '12 at 22:16
  • wow. excellent point, Rob. I'm going to post an answer with these suggestions for others to find – danh Dec 22 '12 at 22:30

3 Answers3

5

As @H2CO3 mentioned, your method of going about producing singletons is acceptable, however not threadsafe. The more traditional approach is to wrap your assignment and comparison in an @synchronized block so multiple thread access is reduced, however overriding +alloc is not the best way of going about implementing an already shaky pattern.

CodaFi
  • 43,043
  • 8
  • 107
  • 153
  • I always think about rewriting singletons as class methods, however it feels quirky. I don't like class methods. –  Dec 22 '12 at 21:05
  • @H2CO3 Well that's what they should be. I guess this variation of the pattern unsettles me because you can easily get confused as to what is a singleton and what isn't without seeing the code properly. I guess it's useful in some rare edge-case situations, but... – CodaFi Dec 22 '12 at 21:06
  • Okay - thanks @CodaFi. Is it okay to use the class itself as the lock token? – danh Dec 22 '12 at 21:20
  • `self` is sufficient. I don't believe you can literally use the class as the lock token without some ugly casting. – CodaFi Dec 22 '12 at 21:21
  • Great. Thanks much. (self is the class in +alloc) – danh Dec 22 '12 at 21:26
1

I think that you should take advantage of the initialize method:

+ (void) initialize
{
    _sharedInstance= [[self alloc]init];
}


+ (id)sharedInstance 
{
    return _sharedIntsance;
}
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • The problem with this is that +initialize can be called multiple times (implicitly of course). And I do hope _sharedInstance is a static global and that you are checking for it's existance, otherwise you'll have an N-ton. – CodaFi Dec 22 '12 at 21:11
  • initialize gets called once by the runtime, then if a programmer seeks troubles instead of calling initialize can do this: strcpy(0,"Crash"); – Ramy Al Zuhouri Dec 22 '12 at 21:17
  • Perhaps you should see this then http://stackoverflow.com/questions/3910187/objective-c-initialize-static-method-called-more-that-once – CodaFi Dec 22 '12 at 21:19
  • Thanks @Ramy - also, I'm looking for a safe way to let outsiders use alloc and still have a singleton. Not sure I see how this addresses that. – danh Dec 22 '12 at 21:19
1

In case others come looking, here's a solution that I think integrates all of the good advice:

+ (id)alloc {

    @synchronized(self) {
        if (!_sharedInstance) {
            _sharedInstance = [super alloc];
        }
        return _sharedInstance;
    }
}

- (id)init {

    @synchronized(self) {
        static BOOL init = NO;
        if (!init) {
            init = YES;
            self = [super init];
        }
    }
    return self;
}

Thanks to @H2CO3 for the thread safety issue, @CodaFi for the thread safety prescription and to @Rob Mayoff for dangers with init under arc. I got helped by the best and the brightest today!

danh
  • 62,181
  • 10
  • 95
  • 136
  • It might be better to override `allocWithZone:` instead of `alloc`. It is certainly better to use `dispatch_once` instead of `@synchronized(self)`. See http://stackoverflow.com/questions/9119042/why-does-apple-recommend-to-use-dispatch-once-for-implementing-the-singleton-pat – Cœur Jul 24 '15 at 16:33