我的 Objective-C 单例模式应该是什么样的? [打烊]

My singleton accessor method is usually some variant of:

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

What could I be doing to improve this?

转载于:https://stackoverflow.com/questions/145154/what-should-my-objective-c-singleton-look-like

weixin_41568127
?yb? Since it's unlikely the 'answer' to this question will change any time soon, I'm placing a historical lock on the question. Two reasons 1) Lots of views, votes, and good content 2) To prevent yo-yoing of open/closed. It was a great question for its time, but questions of these types are not appropriate for Stack Overflow. We now have Code Review for checking working code. Please take all discussion of this question to this meta question.
7 年多之前 回复
csdnceshi67
bug^君 What you have is fine, though you could move the global variable declaration into your +instance method (the only place it needs to be used, unless you're allowing it to be set as well) and use a name like +defaultMyClass or +sharedMyClass for your method. +instance isn't intention-revealing.
大约 12 年之前 回复

26个回答

Another option is to use the +(void)initialize method. From the documentation:

The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.

So you could do something akin to this:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}
csdnceshi54
hurriedly% Note that +initialize is called the first time a message is sent to the class or a subclass. So in particular, if you try to access sharedSingleton without calling any class methods, you'll get nil unless you send a dummy message (e.g. (void)[MySingleton class];) first.
接近 8 年之前 回复
csdnceshi74
7*4 You could also use self, rather than MySingleton, this would eliminate the need of subclassing + (void)initialize in subclasses. => sharedSingleton = [[self alloc] init];
接近 8 年之前 回复
csdnceshi53
Lotus@ IMHO initialize is pretty well useless. You have very little control over the when (order might be important but you don't realize that until it changes on you, for example). Also the subclass problem... Also you need multiple methods to deal with the singleton..
8 年多之前 回复
csdnceshi80
胖鸭 In the example above-- why is the init function not actually called on the object? For example in my singleton class, I used to have the old non-thread-safe implementation and am trying to fix it. Log statements show that when implementing the initialize function as above, the - (id)init function of the singleton class is never called.
8 年多之前 回复
csdnceshi52
妄徒之命 The Apple example is for creating transparently strict singletons, which is almost never appropriate. The majority of singletons are for convenience, not requirement (there may be more than one, there just usually isn't; e.g. NSNotificationCenter). For classes that must be singletons, it is usually a programming error to create multiple instances and you should assert in init rather than "fix" the caller's mistake. The only time Apple's example makes sense is when the singleton is an implementation detail that the caller can ignore. At a minimum, the class should be immutable.
大约 9 年之前 回复
csdnceshi55
~Onlooker What about Creating a Singleton Instance in the documention?
大约 9 年之前 回复
csdnceshi65
larry*wei From the docs listed, this is already thread safe. So, the call is once per runtime -- period. This would seem to be the correct, thread-safe, optimally efficient solution.
9 年多之前 回复
weixin_41568126
乱世@小熊 Argh this is horrible, just use Synchronized, that makes the block of code accessible by 1 thread at a time.
9 年多之前 回复
csdnceshi61
derek5. you can override the release method and make it empty. :)
9 年多之前 回复
csdnceshi63
elliott.david Yes, it could simply check to see if sharedSingleton is nil. This is just boilerplate code that would allow you to do other things in the initialize method, such as initialize other static variables.
9 年多之前 回复
csdnceshi56
lrony* I prefer the initialize method myself. That way I can write a simple method getSingleton that can simply return the singleton reference. But why can't your initialize method simply test the singleton reference for nil instead of setting up another boolean?
9 年多之前 回复
weixin_41568131
10.24 What happens if someone inadvertently releases this instance? They effectively deallocate it for the entire process. I think the standard method that overrides the copy/release/autorelease methods is more reliable.
接近 10 年之前 回复
csdnceshi78
程序go This also is required because there could be subclasses. If they don’t override +initialize their superclasses implementation will be called if the subclass is first used.
大约 10 年之前 回复
csdnceshi63
elliott.david Yes, it is a precaution since the function can also be called directly.
11 年多之前 回复
weixin_41568208
北城已荒凉 If the runtime will only ever call this once, what does the BOOL do? Is that a precaution in case someone calls this function explicitly from their code?
11 年多之前 回复

Since Kendall posted a threadsafe singleton that attempts to avoid locking costs, I thought I would toss one up as well:

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

+ (className *) sharedInstance {                                                                    
  while (!sharedInstance) {                                                                          
    className *temp = [[self alloc] init];                                                                 
    if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
      [temp release];                                                                                   
    }                                                                                                    
  }                                                                                                        
  return sharedInstance;                                                                        
}

Okay, let me explain how this works:

  1. Fast case: In normal execution sharedInstance has already been set, so the while loop is never executed and the function returns after simply testing for the variable's existence;

  2. Slow case: If sharedInstance doesn't exist, then an instance is allocated and copied into it using a Compare And Swap ('CAS');

  3. Contended case: If two threads both attempt to call sharedInstance at the same time AND sharedInstance doesn't exist at the same time then they will both initialize new instances of the singleton and attempt to CAS it into position. Whichever one wins the CAS returns immediately, whichever one loses releases the instance it just allocated and returns the (now set) sharedInstance. The single OSAtomicCompareAndSwapPtrBarrier acts as both a write barrier for the setting thread and a read barrier from the testing thread.

csdnceshi58
Didn"t forge bit late of response, but OSAtomicCompareAndSwapPtrBarrier requires a volatile. Perhaps the volatile keyword is to keep the compiler from optimizing away the check? See: stackoverflow.com/a/5334727/449161 and developer.apple.com/library/mac/#documentation/Darwin/Reference/…
接近 8 年之前 回复
csdnceshi51
旧行李 Hmm, why is the volatile qualification here necessary? Since sharedInstance is only initialized once how come we are preventing compiler from caching it in register by using volatile?
接近 9 年之前 回复
csdnceshi54
hurriedly% I generally don't prevent it. There are often valid reasons to allow what is generally a singleton to multiply instantiated, the most commons is for certain types of unit testing. If I really wanted to enforce a single instance I would probably have the init method check to see if the global existed, and if it did I have it release self and return the global.
大约 9 年之前 回复
csdnceshi57
perhaps? Amazing, really enlightening answer! One question though: what should my init method do in your approach? Throwing an exception when sharedInstance is initialized is not a good idea, I believe. What to do then to prevent user calling init directly many times?
大约 9 年之前 回复
weixin_41568127
?yb? Nice answer - the OSAtomic family is a good thing to know about
9 年多之前 回复
csdnceshi73
喵-见缝插针 This is complete overkill for the at-most one time it can happen during an application's lifetime. Nevertheless, it is spot-on correct, and the compare-and-swap technique is a useful tool to know about, so +1.
10 年多之前 回复

Shouln't this be threadsafe and avoid the expensive locking after the first call?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}
csdnceshi57
perhaps? The double-checked locking technique used here is often a real problem in some environments (see aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf or Google it). Until shown otherwise, I'd assume that Objective-C isn't immune. Also see wincent.com/a/knowledge-base/archives/2006/01/….
10 年多之前 回复

How about

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

So you avoid the synchronization cost after initialization?

weixin_41568196
撒拉嘿哟木头 See discussions of Dual Checked Locking in other answers.
7 年多之前 回复

For an in-depth discussion of the singleton pattern in Objective-C, look here:

Using the Singleton Pattern in Objective-C

Edit: This implementation obsoleted with ARC. Please have a look at How do I implement an Objective-C singleton that is compatible with ARC? for correct implementation.

All the implementations of initialize I've read in other answers share a common error.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

The Apple documentation recommend you check the class type in your initialize block. Because subclasses call the initialize by default. There exists a non-obvious case where subclasses may be created indirectly through KVO. For if you add the following line in another class:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

Objective-C will implicitly create a subclass of MySingletonClass resulting in a second triggering of +initialize.

You may think that you should implicitly check for duplicate initialization in your init block as such:

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

But you will shoot yourself in the foot; or worse give another developer the opportunity to shoot themselves in the foot.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL;DR, here's my implementation

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

(Replace ZAssert with our own assertion macro; or just NSAssert.)

csdnceshi79
python小菜 I would just live simpler and avoid initialize altogether.
8 年多之前 回复
static MyClass *sharedInst = nil;

+ (id)sharedInstance
{
    @synchronize( self ) {
        if ( sharedInst == nil ) {
            /* sharedInst set up in init */
            [[self alloc] init];
        }
    }
    return sharedInst;
}

- (id)init
{
    if ( sharedInst != nil ) {
        [NSException raise:NSInternalInconsistencyException
            format:@"[%@ %@] cannot be called; use +[%@ %@] instead"],
            NSStringFromClass([self class]), NSStringFromSelector(_cmd), 
            NSStringFromClass([self class]),
            NSStringFromSelector(@selector(sharedInstance)"];
    } else if ( self = [super init] ) {
        sharedInst = self;
        /* Whatever class specific here */
    }
    return sharedInst;
}

/* These probably do nothing in
   a GC app.  Keeps singleton
   as an actual singleton in a
   non CG app
*/
- (NSUInteger)retainCount
{
    return NSUIntegerMax;
}

- (oneway void)release
{
}

- (id)retain
{
    return sharedInst;
}

- (id)autorelease
{
    return sharedInst;
}
csdnceshi60
℡Wang Yan Subverting init like this is a pretty ugly approach IMO. Don't mess with init and/or the actual creation of the object. If you instead go for a controlled point of access to a shared instance, while not hard-baking singleton into the object, you'll have a happier time later if writing tests etc. Hard singletons are far too overused.
接近 8 年之前 回复
csdnceshi53
Lotus@ I noticed that clang complains about a leak if you don't assign the result of [[self alloc] init] to sharedInst.
11 年多之前 回复

Short answer: Fabulous.

Long answer: Something like....

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

+ (id) instance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == NULL){
            instance = [[super allocWithZone:NULL] init];
        }
    });
    return instance;
}

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

- (id) copyWithZone:(NSZone *)paramZone {
    return self;
}

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

Be sure to read the dispatch/once.h header to understand what's going on. In this case the header comments are more applicable than the docs or man page.

I've rolled singleton into a class, so other classes can inherit singleton properties.

Singleton.h :

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

Singleton.m :

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

+ (id) sharedInstance:(id*)inst {
    @synchronized(self)
    {
        if (*inst == nil)
            *inst = [[self alloc] init];
    }
    return *inst;
}

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
    @synchronized(self) {
        if (*inst == nil) {
            *inst = [super allocWithZone:zone];
            return *inst;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

And here is an example of some class, that you want to become singleton.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

The only limitation about Singleton class, is that it is NSObject subclass. But most time I use singletons in my code they are in fact NSObject subclasses, so this class really ease my life and make code cleaner.

csdnceshi70
笑故挽风 You might want to use some other locking mechanism because @synchronized is horribly slow and should be avoided.
8 年多之前 回复

Here's a macro that I put together:

http://github.com/cjhanson/Objective-C-Optimized-Singleton

It is based on the work here by Matt Gallagher But changing the implementation to use method swizzling as described here by Dave MacLachlan of Google.

I welcome comments / contributions.

csdnceshi76
斗士狗 the link seems broken - where can I get that source?
大约 10 年之前 回复
共26条数据 1 3 尾页
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐