我的 Objective-C 单例应该是什么样子的?[关闭]

What should my Objective-C singleton look like? [closed]

提问人:schwa 提问时间:9/28/2008 最后编辑:Rachelschwa 更新时间:3/12/2013 访问量:184999

问:

已锁定。这个问题及其答案被锁定,因为这个问题偏离了主题,但具有历史意义。它目前不接受新的答案或交互。

我的单例访问器方法通常是以下的某种变体:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

我能做些什么来改善这一点?

Objective-C 设计模式 单例 对象初始值设定项

评论

27赞 Chris Hanson 9/28/2008
你所拥有的很好,尽管你可以将全局变量声明移动到你的 +instance 方法中(唯一需要使用它的地方,除非你也允许设置它),并为你的方法使用类似 +defaultMyClass 或 +sharedMyClass 的名称。+实例不是故意透露的。
0赞 George Stocker 3/22/2013
由于这个问题的“答案”不太可能很快改变,所以我对这个问题进行了历史性的锁定。两个原因 1) 大量的浏览量、投票和良好的内容 2) 防止打开/关闭的溜溜球。这在当时是一个很好的问题,但这些类型的问题不适合 Stack Overflow。我们现在有代码审查来检查工作代码。请把这个问题的所有讨论都带到这个元问题上。

答:

95赞 Ben Hoffstein 9/28/2008 #1
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[查看模板]

评论

7赞 Chris Hanson 9/28/2008
这就是您通常应该用于单例的全部内容。除其他外,保持类可单独实例化使它们更易于测试,因为您可以测试单独的实例,而不是重置其状态。
3赞 MechEthan 7/14/2011
Stig Brautaset:不,在这个例子中省略@synchronized是不行的。它在那里处理两个线程同时执行此静态函数的可能竞争条件,它们同时通过了“if(!sharedSingleton)”测试,从而产生了两个 [MySingleton alloc]...@synchronized {scope block} 强制假设的第二个线程等待第一个线程退出 {scope block},然后才被允许继续进入该线程。我希望这会有所帮助!=)
3赞 lindon fox 10/28/2011
是什么阻止了某人仍然创建自己的对象实例?MySingleton *s = [[MySingelton alloc] init];
1赞 Raffi Khatchadourian 12/21/2011
@lindonfox 你的问题的答案是什么?
1赞 lindon fox 12/21/2011
@Raffi - 对不起,我想我一定忘了粘贴我的答案。不管怎样,我拿到了这本书,它详细说明了你如何制作一个“严格”的辛格尔顿。基本上,由于您无法将启动方法设为私有,因此您需要覆盖方法 alloc 和 copy。因此,如果你尝试做类似的事情,你会得到一个运行时错误(尽管不幸的是不是编译时错误)。我不明白对象创建的所有细节是如何实现的,但是您实现了在Pro Objective-C Design Patterns for iOS[[MySingelton alloc] init]+ (id) allocWithZone:(NSZone *)zonesharedSingleton
14赞 Michael Nickerson 9/28/2008 #2
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;
}

评论

3赞 pix0r 5/7/2009
我注意到,如果您不将结果分配给 sharedInst,clang会抱怨泄漏。[[self alloc] init]
0赞 occulus 1/14/2013
像这样颠覆 init 是一种非常丑陋的方法 IMO。不要弄乱 init 和/或对象的实际创建。如果你选择对共享实例的受控访问点,同时不将单一实例硬烘焙到对象中,那么如果编写测试等,你以后会更快乐。硬单例被过度使用。
2赞 lajos 9/28/2008 #3

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

@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
59赞 Colin Barrett 9/28/2008 #4

根据我下面的其他答案,我认为你应该这样做:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}

评论

6赞 Chris Hanson 9/28/2008
不要为上面所做的所有事情而烦恼。使你的(希望极少的)单例可单独实例化,并且只有一个共享/默认方法。只有当你真的、真的、只想要你的类的一个实例时,你所做的事情才是必要的。你没有,特别是对于单元测试。
0赞 Colin Barrett 10/23/2008
问题是,这是用于“创建单例”的 Apple 示例代码。但是,是的,你是绝对正确的。
1赞 Luke Redpath 7/25/2009
如果你想要一个“真正的”单例(即一个只能实例化一次的对象),Apple 示例代码是正确的,但正如 Chris 所说,这很少是你想要或需要的,而某种可设置的共享实例是你通常想要的。
0赞 Kobski 1/31/2012
下面是上述方法的宏:gist.github.com/1057420。这就是我使用的。
1赞 CodeSmile 4/7/2012
撇开单元测试不谈,没有什么反对这个解决方案的,对吧?而且它既快速又安全。
207赞 Robbie Hanson 12/5/2008 #5

另一种选择是使用该方法。从文档中:+(void)initialize

运行时在类或从该类继承的任何类从程序内部发送其第一条消息之前,恰好向程序中的每个类发送一次。(因此,如果不使用该类,则可能永远不会调用该方法。运行时以线程安全的方式将消息发送到类。超类在其子类之前收到此消息。initializeinitialize

因此,您可以执行类似于以下内容的操作:

static MySingleton *sharedSingleton;

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

评论

7赞 Aftermathew 4/4/2009
如果运行时只调用一次,BOOL 会做什么?如果有人从他们的代码中显式调用此函数,这是一种预防措施吗?
5赞 Robbie Hanson 4/8/2009
是的,这是一种预防措施,因为该函数也可以直接调用。
33赞 Sven 9/7/2010
这也是必需的,因为可能存在子类。如果它们不覆盖其超类,则在首次使用子类时将调用实现。+initialize
3赞 3/26/2011
@Paul,您可以重写该方法并将其设置为空。:)release
4赞 lilbyrdie 6/23/2011
@aryaxt:从列出的文档来看,这已经是线程安全的了。因此,调用是每个运行时 -- 周期一次。这似乎是正确的、线程安全的、最高效的解决方案。
10赞 Matthieu Cormier 6/23/2009 #6

博客 Cocoa With Love 上有对 Singleton 宏代码的详尽解释

http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html

-4赞 Gregory Higley 6/24/2009 #7

我通常使用的代码与Ben Hoffstein的答案大致相似(我也是从维基百科上得到的)。我使用它的原因 克里斯·汉森 (Chris Hanson) 在他的评论中陈述。

但是,有时我需要将单例放入 NIB 中,在这种情况下,我使用以下命令:

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

我将(等)的实现留给读者,尽管上述代码是您在垃圾回收环境中所需要的。-retain

评论

2赞 Mecki 6/29/2009
您的代码不是线程安全的。它在 alloc 方法中使用 synchronized,但在 init 方法中不使用。检查初始化的布尔值不是线程安全的。
0赞 2 revsRob Dotson #8

你不想在自己身上同步......因为自我对象还不存在!您最终会锁定一个临时 id 值。您希望确保没有其他人可以运行类方法( sharedInstance, alloc, allocWithZone: 等),因此您需要在类对象上同步:

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end

评论

1赞 Rob Dotson 1/14/2010
其余的方法、访问器方法、赋值器方法等应该在自身上同步。所有 class(+) 方法和初始值设定项(可能还有 -dealloc)都应该在类对象上同步。如果使用 Objective-C 2.0 属性而不是访问器/赋值器方法,则可以避免手动同步。所有 object.property 和 object.property = foo 都会自动同步到 self。
3赞 dreamlax 2/19/2010
请解释为什么您认为该对象在类方法中不存在。运行时根据它为每个方法(类或实例)提供的完全相同的值来确定要调用的方法实现。selfself
2赞 jscs 9/27/2011
在类方法的内部,类对象。自己试一试:self#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
9赞 Kendall Helmstetter Gelner 2/19/2010 #9

我在sharedInstance上有一个有趣的变体,它是线程安全的,但在初始化后不会锁定。我还没有足够的把握来修改要求的顶级答案,但我提出来供进一步讨论:

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

评论

1赞 Dave DeLong 2/19/2010
+1 这真的很有趣。我可能会用它来转换为 的克隆。这样你就再也不用担心再买锁了。class_replaceMethodsharedInstancesimpleSharedInstance@synchronized
0赞 Kendall Helmstetter Gelner 2/19/2010
效果是一样的,使用 exchangeImplementations 意味着在调用 sharedInstance 时 init 之后,实际上是在调用 simpleSharedInstance。我实际上是从 replaceMethod 开始的,但决定最好只是切换实现,这样如果需要,原始实现仍然存在......
0赞 Kendall Helmstetter Gelner 2/19/2010
在进一步的测试中,我无法让 replaceMethod 工作 - 在重复调用中,代码仍然调用原始 sharedInstance 而不是 simpleSharedInstance。我认为这可能是因为它们都是类级方法......我使用的替换是:class_replaceMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));以及其中的一些变体。我可以验证我发布的代码是否有效,并且在第一次通过 sharedInstance 后调用 simpleSharedInstance。
0赞 Louis Gerbarg 3/16/2010
你可以制作一个线程安全版本,在初始化后不支付锁定成本,而无需做一堆运行时清理,我在下面发布了一个实现。
1赞 Sven 9/7/2010
+1 好主意。我只是喜欢那些可以用运行时做的事情。但在大多数情况下,这可能是过早的优化。如果我真的必须摆脱同步成本,我可能会使用 Louis 的无锁版本。
2赞 CJ Hanson 3/2/2010 #10

这是我放在一起的一个宏

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

它基于 Matt Gallagher 在这里的工作,但将实现更改为使用 Google 的 Dave MacLachlan 在此处描述的方法 swizzing

我欢迎评论/贡献。

评论

0赞 amok 8/30/2010
链接似乎已损坏 - 我在哪里可以获得该来源?
58赞 Louis Gerbarg 3/16/2010 #11

由于 Kendall 发布了一个试图避免锁定成本的线程安全单例,我想我也会扔掉一个:

#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;                                                                        
}

好的,让我解释一下这是如何工作的:

  1. 快速情况:在正常执行中已经设置好了,所以循环永远不会执行,函数在简单地测试变量是否存在后返回;sharedInstancewhile

  2. 慢速情况:如果不存在,则使用比较和交换 ('CAS') 分配实例并将其复制到其中;sharedInstance

  3. 争用情况:如果两个线程都尝试同时调用并且同时不存在,那么它们都将初始化单例的新实例并尝试将其 CAS 到位。无论哪个赢了,CAS 都会立即返回,无论哪个输了,都会释放它刚刚分配的实例并返回 (now set) 。单个既充当设置线程的写入屏障,又充当测试线程的读取屏障。sharedInstancesharedInstancesharedInstanceOSAtomicCompareAndSwapPtrBarrier

评论

18赞 Steve Madsen 4/22/2010
这完全是矫枉过正,因为它在应用程序的生命周期中最多可能发生一次。尽管如此,它是正确的,比较和交换技术是一个有用的工具,所以+1。
0赞 Bill 2/27/2011
不错的答案 - OSAtomic 系列是一件值得了解的好事
1赞 matm 9/2/2011
@Louis:令人惊叹,非常有启发性的答案!但有一个问题:我的方法应该在你的方法中做什么?我相信,在初始化时抛出异常不是一个好主意。那么该怎么做才能防止用户多次直接调用呢?initsharedInstanceinit
2赞 Louis Gerbarg 9/2/2011
我通常不会阻止它。通常有正当的理由允许通常的单例进行乘以实例化,最常见的是用于某些类型的单元测试。如果我真的想强制执行单个实例,我可能会让 init 方法检查全局是否存在,如果存在,我会让它释放 self 并返回全局。
1赞 Ben Flynn 11/17/2012
@Tony延迟响应,但 OSAtomicCompareAndSwapPtrBarrier 需要 volatile。也许 volatile 关键字是为了防止编译器优化检查?请参阅:stackoverflow.com/a/5334727/449161developer.apple.com/library/mac/#documentation/Darwin/Reference/...
2赞 Jompe 3/17/2010 #12

这难道不是线程安全的,并避免了第一次调用后昂贵的锁定吗?

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

评论

2赞 Steve Madsen 4/22/2010
在某些环境中,此处使用的双重检查锁定技术通常是一个真正的问题(请参阅 aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 或谷歌)。除非另有说明,否则我认为 Objective-C 不能幸免。另请参阅 wincent.com/a/knowledge-base/archives/2006/01/...
2赞 Fred McCann 5/22/2010 #13

有关 Objective-C 中单例模式的深入讨论,请查看此处:

在 Objective-C 中使用单例模式

0赞 user370199 6/18/2010 #14
static mySingleton *obj=nil;

@implementation mySingleton

-(id) init {
    if(obj != nil){     
        [self release];
        return obj;
    } else if(self = [super init]) {
        obj = self;
    }   
    return obj;
}

+(mySingleton*) getSharedInstance {
    @synchronized(self){
        if(obj == nil) {
            obj = [[mySingleton alloc] init];
        }
    }
    return obj;
}

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

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

- (void)release {
    if(obj != self){
        [super release];
    }
    //do nothing
}

- (id)autorelease {
    return self;
}

-(void) dealloc {
    [super dealloc];
}
@end
5赞 obscenum 11/30/2010 #15

我已将单例合并到一个类中,因此其他类可以继承单例属性。

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

这里有一个类的例子,你想成为单例。

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

关于 Singleton 类的唯一限制是它是 NSObject 子类。但大多数时候我在代码中使用单例,它们实际上是 NSObject 子类,所以这个类真的让我的生活变得轻松,使代码更简洁。

评论

0赞 DarkDust 4/17/2012
您可能希望使用其他锁定机制@synchronized因为它非常慢,应避免使用。
0赞 Dan Rosenstark 1/17/2011 #16

只是想把这个留在这里,这样我就不会失去它。这个的优点是它可以在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;
}
-5赞 deleted_user 1/24/2011 #17

公认的答案虽然是编译的,但却是不正确的。

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

根据 Apple 文档:

...可以采用类似的方法来同步关联类的类方法,使用 Class 对象而不是 self。

即使使用self works,也不应该,这对我来说看起来像是一个复制和粘贴错误。 类工厂方法的正确实现是:

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

评论

6赞 schwa 1/26/2011
self 肯定确实存在 it 类范围。它指的是类,而不是类的实例。类(大部分)是第一类对象。
0赞 user4951 5/4/2011
为什么要@synchroninzed WITHIN 一个方法?
1赞 jscs 9/27/2011
正如 schwa 已经说过的,类方法内部的类对象。请参阅我的评论以获取演示这一点的片段。self
0赞 quellish 2/9/2012
self存在,但将其用作传递给的标识符将同步对实例方法的访问。正如 @user490696 所指出的,在某些情况下(如单例),使用类对象是可取的。摘自 Obj-C 编程指南:@synchronizedYou can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
12赞 lorean 6/8/2011 #18

编辑:此实现已随 ARC 而过时。请查看如何实现与 ARC 兼容的 Objective-C 单例?以正确实现。

我在其他答案中读到的所有初始化实现都有一个共同的错误。

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

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

Apple 文档建议您检查 initialize 块中的类类型。因为子类默认调用 initialize。存在一种不明显的情况,即可以通过 KVO 间接创建子类。如果您在另一个类中添加以下行:

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

Objective-C 将隐式创建 MySingletonClass 的子类,从而导致第二次触发 。+initialize

您可能认为应该隐式检查init块中的重复初始化,如下所示:

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

但你会搬起石头砸自己的脚;或者更糟糕的是,让另一个开发人员有机会搬起石头砸自己的脚。

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

TL;DR,这是我的实现

@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

(将 ZAssert 替换为我们自己的断言宏;或者只是 NSAssert。

评论

1赞 Tom Andersen 5/17/2012
我只会生活得更简单,完全避免初始化。
0赞 Nate 11/23/2011 #19

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

以下是我根据我见过的几个 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

为什么界面宏几乎是空的?头文件和代码文件之间的代码一致性;可维护性,以防您想添加更多自动方法或对其进行更改。

我正在使用 initialize 方法来创建单例,就像这里最流行的答案中使用的一样(在撰写本文时)。

2赞 Tony 12/30/2011 #20

怎么样

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

所以你避免了初始化后的同步成本?

评论

0赞 i_am_jorf 3/7/2013
请参阅其他答案中有关双重检查锁定的讨论。
6赞 quellish 2/8/2012 #21

简短的回答:太棒了。

长答案:像......

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

请务必阅读 dispatch/once.h 标头以了解发生了什么。在这种情况下,标题注释比文档或手册页更适用。

0赞 chunkyguy 2/11/2012 #22

使用 Objective C 类方法,我们可以避免以通常的方式使用单例模式,如下所示:

[[Librarian sharedInstance] openLibrary]

自:

[Librarian openLibrary]

通过将类包装在另一个只有类方法的类中,这样就不会意外创建重复的实例,因为我们没有创建任何实例!

在这里写了一篇更详细的博客:)

评论

0赞 i_am_jorf 3/7/2013
您的链接不再起作用。
0赞 JJD 3/23/2012 #23

扩展 @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;
}
1赞 kevinlawler 5/4/2012 #24

KLSingleton 是:

  1. 子类(到第n级)
  2. ARC兼容
  3. 安全,和allocinit
  4. 延迟加载
  5. 线程安全
  6. 无锁(使用 +initialize,而不是 @synchronize)
  7. 无宏
  8. 无旋转
  9. 简单

KL辛格尔顿

评论

1赞 Oleg Trakhman 7/4/2012
我正在使用您的NSSingleton进行我的项目,它似乎与KVO不兼容。问题是 KVO 为每个 KVO 对象创建子类,并在其前缀为 NSKVONotifying_MyClass。它使 MyClass +initialize 和 -init 方法被调用两次。
0赞 kevinlawler 7/7/2012
我在最新的 Xcode 上对此进行了测试,在注册或接收 KVO 事件时没有任何问题。您可以使用以下代码进行验证: gist.github.com/3065038 正如我在 Twitter 上提到的,对 NSSingleton 调用一次 +initialize 方法,对每个子类调用一次。这是 Objective-C 的一个属性。
0赞 kevinlawler 7/7/2012
如果添加到该方法中,则可以验证类是否仅初始化一次。NSLog(@"initialize: %@", NSStringFromClass([self class]));+initialize
0赞 Oleg Trakhman 7/7/2012
NSLog(@“初始化: %@”, NSStringFromClass([self class]));
0赞 Dan Rosenstark 9/6/2012
您可能还希望它与 IB 兼容。我的是:stackoverflow.com/questions/4609609/......
0赞 TienDC 12/24/2012 #25

我的方法很简单,如下所示:

static id instanceOfXXX = nil;

+ (id) sharedXXX
{
    static volatile BOOL initialized = NO;

    if (!initialized)
    {
        @synchronized([XXX class])
        {
            if (!initialized)
            {
                instanceOfXXX = [[XXX alloc] init];
                initialized = YES;
            }
        }
    }

    return instanceOfXXX;
}

如果单例已经初始化,则不会输入 LOCK 块。第二个检查 if(!initialized) 是确保当当前线程获取 LOCK 时它尚未初始化。

评论

0赞 i_am_jorf 3/7/2013
目前尚不清楚标记为是否足够。请参见 aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdfinitializedvolatile
0赞 Zolt 2/19/2013 #26

我还没有通读所有的解决方案,所以如果这段代码是多余的,请原谅。

在我看来,这是最线程安全的实现。

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}