Correct Singleton Pattern Objective C (iOS)?
Asked Answered
H

3

29

I found some information in the net to create a singleton class using GCD. Thats cool because it's thread-safe with very low overhead. Sadly I could not find complete solutions but only snippets of the sharedInstance method. So I made my own class using the trial and error method - and et voila - the following came out:

@implementation MySingleton

// MARK: -
// MARK: Singleton Pattern using GCD

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; }
- (id)copyWithZone:(NSZone *)zone { return self; }
- (id)autorelease { return self; }
- (oneway void)release { /* Singletons can't be released */ }
- (void)dealloc { [super dealloc]; /* should never be called */ }
- (id)retain { return self; }
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ }

+ (MySingleton *)sharedInstance
{
    static MySingleton * instance = nil;

    static dispatch_once_t predicate;   
    dispatch_once(&predicate, ^{
        // --- call to super avoids a deadlock with the above allocWithZone
        instance = [[super allocWithZone:nil] init];
    });

    return instance;
}

// MARK: -
// MARK: Initialization

- (id)init
{
    self = [super init];
    if (self) 
    {
        // Initialization code here.
    }
    return self;
}

@end

Please feel free to comment and tell me if I've missing something or doing something completely wrong ;)

Cheers Stefan

Hymie answered 29/9, 2011 at 14:26 Comment(4)
I'd be tempted to add a -(void)dealloc that throws an exception, that way you should be able to track down the offending actor if someone is getting a singleton instance and then releasing it. Apart from being an abuse of the pattern, that'd leave you with a dangling pointer.Abranchiate
meta question: should this be at [codereview.stackexchange.com/]?Lists
apple strongly advises not to create singletons overriding retain/release! this will break applications transitioning to ARCVitiligo
Possible duplicate/closely related: What does your Objective-C singleton look like?Betseybetsy
F
82

Keep it simple:

+(instancetype)sharedInstance
{
    static dispatch_once_t pred;
    static id sharedInstance = nil;
    dispatch_once(&pred, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (void)dealloc
{
    // implement -dealloc & remove abort() when refactoring for
    // non-singleton use.
    abort();
}

That is it. Overriding retain, release, retainCount and the rest is just hiding bugs and adding a bunch of lines of unnecessary code. Every line of code is a bug waiting to happen. In reality, if you are causing dealloc to be called on your shared instance, you have a very serious bug in your app. That bug should be fixed, not hidden.

This approach also lends itself to refactoring to support non-singleton usage modes. Pretty much every singleton that survives beyond a few releases will eventually be refactored into a non-singleton form. Some (like NSFileManager) continue to support a singleton mode while also supporting arbitrary instantiation.

Note that the above also "just works" in ARC.

Francinefrancis answered 29/9, 2011 at 15:27 Comment(6)
thanks for this...just a question here regarding the statically created object. Shouldnt you release that object i dealloc? i am always confused about the ownership of static objects. So shouldnt you have something like [[MyClass sharedInstance] release]; in dealloc?Interrogatory
Singletons exist from the moment they are requested until the app terminates. They aren't deallocated and re-instantiated. As there is no reason to deallocate anything on application termination, there is no reason to release the singleton. Because it is unlikely that the singleton's destruction has ever been tested, implementing the dealloc as shown is a purely defensive measure to remind future you that past you didn't think about memory management for this class.Francinefrancis
one small addition, the (id) should be (instanceType)..for details read this questionPediatrician
Hello, can you do that using swift language? Thanks.Heber
@Allan, I've added another answer for creating a singleton with Swift.Uriia
why do you use [[self class] alloc] instead of [self alloc] in + method?Spam
I
19
// See Mike Ash "Care and Feeding of Singletons"
// See Cocoa Samurai "Singletons: You're doing them wrong"
+(MySingleton *)singleton {
    static dispatch_once_t pred;
    static MySingleton *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[MySingleton alloc] init];
        shared.someIvar = @"blah";
    });
    return shared;
}

Be aware that dispatch_once is not reentrant, so calling itself from inside the dispatch_once block will deadlock the program.

Don't try to code defensively against yourself. If you are not coding a framework, treat your class as normal then stick the singleton idiom above. Think of the singleton idiom as a convenience method, not as a defining trait of your class. You want to treat your class as a normal class during unit testing, so it's OK to leave an accessible constructor.

Don't bother using allocWithZone:

  • It ignores its argument and behaves exactly like alloc. Memory zones are no longer used in Objective-C so allocWithZone: is only kept for compatibility with old code.
  • It doesn't work. You can't enforce singleton behavior in Objective-C because more instances can always be created using NSAllocateObject() and class_createInstance().

A singleton factory method always returns one of these three types:

  • id to indicate the return type is not fully known (case where you are building a class cluster).
  • instancetype to indicate that the type returned is an instance of the enclosing class.
  • The class name itself (MySingleton in the example) to keep it simple.

Since you tagged this iOS, an alternative to a singleton is saving the ivar to the app delegate and then using a convenience macro that you can redefine if you change your mind:

#define coreDataManager() \
        ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager
Ingenuity answered 29/9, 2011 at 14:41 Comment(0)
B
1

If you want to unit test your singleton you also have to make it so that you can replace it with a mock singleton and/or reset it to the normal one:

@implementation ArticleManager

static ArticleManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;

+(ArticleManager *)sharedInstance {
    dispatch_once(&once_token, ^{
        if (_sharedInstance == nil) {
            _sharedInstance = [[ArticleManager alloc] init];
        }
    });
    return _sharedInstance;
}

+(void)setSharedInstance:(ArticleManager *)instance {
    once_token = 0; // resets the once_token so dispatch_once will run again
    _sharedInstance = instance;
}

@end
Boehmer answered 25/1, 2013 at 1:29 Comment(1)
I walk through each line of that code sample in a blog post here: twobitlabs.com/2013/01/…Boehmer

© 2022 - 2024 — McMap. All rights reserved.