我的Objective-C单例应该长什么样?

333

我的单例访问方法通常是以下变体之一:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

我应该做些什么来改进这个呢?


27
你的代码已经可以使用,但你可以将全局变量声明放到 +instance 方法中(除非你允许设置它,否则它只在此处使用),并使用像 +defaultMyClass 或 +sharedMyClass 这样的名称来命名你的方法。+instance 不明确表达意图。 - Chris Hanson
由于这个问题的“答案”不太可能在短时间内发生改变,我将在该问题上放置历史锁定。有两个原因:1)有很多浏览量、投票和好的内容;2)防止开/关状态的来回变化。这是一个非常好的问题,但这类问题不适合在Stack Overflow上提问。我们现在有Code Review可以检查工作代码。请将所有关于此问题的讨论转到此元问题 - George Stocker
26个回答

2

这个代码应该是线程安全的,第一次调用后就避免了昂贵的锁定操作,是吗?

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

2
这里使用的双重检查锁定技术在某些环境中经常是一个真正的问题(请参见http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf或Google它)。除非有其他证明,否则我会认为Objective-C也不免疫。还请参阅http://www.wincent.com/a/knowledge-base/archives/2006/01/locking_doublec.php。 - Steve Madsen

2

2

链接似乎已经失效了 - 我在哪里可以获取源代码? - amok

2

这在非垃圾回收环境中也适用。

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

+(MySingleton*)sharedManager {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedMySingleton;
}


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


-(void)dealloc {
    [super dealloc];
}

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


-(id)retain {
    return self;
}


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


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


-(id)init {
    self = [super init];
    sharedMySingleton = self;

    //initialize here

    return self;
}

@end

2
如何呢?
static MyClass *gInstance = NULL;

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

    return(gInstance);
}

那么在初始化后,您就可以避免同步成本了吗?


请参阅其他答案中有关双重检查锁定的讨论。 - i_am_jorf

1
KLSingleton 是:
  1. 可无限子类化
  2. 与 ARC 兼容
  3. 在使用 allocinit 时安全
  4. 懒加载
  5. 线程安全
  6. 无锁(使用 +initialize,而非 @synchronize)
  7. 无宏定义
  8. 无方法交换
  9. 简单易用

KLSingleton


1
我正在使用你们的NSSingleton来进行我的项目,但是它似乎与KVO不兼容。问题在于KVO为每个KVO对象创建一个子类,并在其前缀中添加NSKVONotifying_MyClass。这会导致MyClass的+initialize和-init方法被调用两次。 - Oleg Trakhman
我在最新的Xcode上进行了测试,注册和接收KVO事件都没有任何问题。您可以使用以下代码进行验证:https://gist.github.com/3065038 正如我在Twitter上提到的那样,+initialize方法对于NSSingleton只调用一次,对于每个子类也调用一次。这是Objective-C的一个特性。 - kevinlawler
如果您将 NSLog(@"initialize: %@", NSStringFromClass([self class])); 添加到 +initialize 方法中,则可以验证类仅初始化一次。 - kevinlawler
NSLog(@"初始化: %@", NSStringFromClass([self class])); - Oleg Trakhman
你可能也想让它与IB兼容。我的是:https://dev59.com/XFPTa4cB1Zd3GeqPgibC - Dan Rosenstark

0

延伸@robbie-hanson的例子...

static MySingleton* sharedSingleton = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        sharedSingleton = [[self alloc] init];
    }
}

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

0

只是想把这个留在这里,以免丢失。这个的优点是它可以在InterfaceBuilder中使用,这是一个巨大的优势。这是从我提出的另一个问题中获取的

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}

0

通过Objective C类方法,我们可以避免使用单例模式的常规方式,从:

[[Librarian sharedInstance] openLibrary]

至:

[Librarian openLibrary]

通过将该类包装在另一个仅具有类方法的类中,就不会意外创建重复实例,因为我们没有创建任何实例!
我写了一篇更详细的博客这里 :)

您的链接已失效。 - i_am_jorf

0

我知道这个“问题”有很多评论,但我没有看到很多人建议使用宏来定义单例。这是一个非常常见的模式,宏极大地简化了单例。

这里是我根据几个Objc实现编写的宏。

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

使用示例:

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

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

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

为什么在接口宏中几乎没有内容?这是为了保持头文件和代码文件之间的代码一致性,以及在需要添加更多自动方法或进行修改时的可维护性。
我正在使用初始化方法来创建单例,这是目前最流行的答案之一(截至撰写本文时)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接