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


334

我的单例访问器方法通常是:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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


27
尽管您可以将全局变量声明移至您的+ instance方法中(除非您也允许对其进行设置,否则它是唯一需要使用的地方),并使用+ defaultMyClass或+您的方法的sharedMyClass。+实例不是意图泄露。
克里斯·汉森

由于此问题的“答案”不太可能在短期内改变,因此我将历史锁定在该问题上。两个原因1)大量的视图,票和良好的内容2)防止打开/关闭的溜溜球。这是一个很长时间的问题,但是这些类型的问题不适用于Stack Overflow。现在,我们有了“代码审查”,用于检查工作代码。请把这个问题的所有讨论都带到这个元问题上
乔治·斯托克

Answers:


207

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

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

因此,您可以执行以下操作:

static MySingleton *sharedSingleton;

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

7
如果运行时仅调用一次,那么BOOL会做什么?如果有人从他们的代码中显式调用此函数,这是一种预防措施吗?
Aftermathew

5
是的,这是一种预防措施,因为该函数也可以直接调用。
罗比·汉森

33
这也是必需的,因为可能存在子类。如果它们不重写,+initialize则在第一次使用子类的情况下将调用实现。
斯文(Sven)2010年

3
@Paul,您可以覆盖该release方法并将其设为空。:)

4
@aryaxt:从列出的文档来看,这已经是线程安全的。因此,该调用在每个运行时间周期内都是一次。这似乎是正确的,线程安全的,最有效的解决方案。
lilbyrdie 2011年

95
@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
这就是您通常应用于单例的所有内容。除其他事项外,将类分别实例化可以使它们更易于测试,因为您可以测试单独的实例,而不用重置其状态的方法。
克里斯·汉森

3
Stig Brautaset:不,在此示例中,忽略@synchronized是不可行的。它可以处理两个线程同时执行此静态函数的可能竞争条件,它们都同时通过“ if(!sharedSingleton)”测试,从而导致两个[MySingleton alloc]。 .. @synchronized {作用域块}强制假设的第二个线程等待第一个线程退出{作用域块},然后再允许其进入。我希望这有帮助!=)
MechEthan 2011年

3
是什么阻止某人仍然制作自己的对象实例?MySingleton *s = [[MySingelton alloc] init];
林登·福克斯2011年

1
@lindonfox您的问题的答案是什么?
拉菲·哈查杜安

1
@Raffi-对不起,我想我一定忘了粘贴答案。无论如何,我得到了这本书Pro Objective-C Design Patterns for iOS,它阐明了您如何制作“严格的” singelton。基本上,由于您不能将启动方法设为私有,因此需要覆盖方法alloc和copy。因此,如果您尝试执行类似的操作[[MySingelton alloc] init],则将遇到运行时错误(但不幸的是,不是编译时错误)。我不了解对象创建的所有详细信息,但是您实现了+ (id) allocWithZone:(NSZone *)zone这一点sharedSingleton
lindon fox 2011年

59

根据我在下面的其他回答,我认为您应该这样做:

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

6
不要理会上面的所有操作。使您的(希望极少)单身人士单独实例化,并且只有共享/默认方法。仅当您确实确实仅希望类的单个实例时,才需要执行操作。特别是你不知道的 用于单元测试。
克里斯·汉森

问题是这是Apple“创建单例”的示例代码。但是,是的,你是绝对正确的。
科林·巴雷特

1
如果您想要一个“真实的”单例(即一个对象只能实例化一次),Apple示例代码是正确的,但是正如克里斯所说,这几乎不是您想要或需要的,而某种可设置的共享实例就是您想要的通常想要。
路加·雷德帕特

这是上述方法的宏:gist.github.com/1057420。这就是我用的。
科布斯基2012年

1
除了单元测试,没有什么反对这种解决方案的,对吗?而且它既快速又安全。
LearnCocos2D 2012年

58

由于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. 最快的情况:在正常执行sharedInstance中已经设置好了,因此while永远不会执行循环,并且仅在测试了变量的存在之后函数才返回。

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

  3. 争鸣情况:如果两个线程都试图打电话给sharedInstance在同一时间 sharedInstance同一时间不存在,那么他们将单身的都初始化新实例,并尝试CAS放到正确位置。赢得CAS的任何人都立即返回,失去CAS的任何人释放它刚刚分配的实例并返回(现在设置)sharedInstance。单个代码OSAtomicCompareAndSwapPtrBarrier既充当设置线程的写屏障,也充当来自测试线程的读屏障。


18
在应用程序的生存期内,这种情况最多可能发生一次,完全是彻底的。不过,它是正确的,并且比较和交换技术是了解的有用工具,因此+1。
史蒂夫·麦森

好的答案-OSAtomic家庭是一件好事,应该知道
Bill

1
@Louis:令人惊奇的答案!但是有一个问题:我的init方法在您的方法中应该做什么?sharedInstance我相信初始化时引发异常不是一个好主意。那么,如何防止用户init多次直接拨打电话呢?
matm 2011年

2
我通常不会阻止它。通常有充分的理由允许通常是单例的实例化乘法,最常见的是某些类型的单元测试。如果我真的想强制执行一个实例,则可能需要使用init方法检查全局变量是否存在,如果确实存在,我可以释放它并返回全局变量。
Louis Gerbarg

1
@Tony的响应晚了一点,但是OSAtomicCompareAndSwapPtrBarrier需要一个volatile。也许volatile关键字是为了防止编译器优化检查?请参阅:stackoverflow.com/a/5334727/449161developer.apple.com/library/mac/#documentation/Darwin/Reference/...
本·弗林

14
静态MyClass * sharedInst = nil;

+(id)sharedInstance
{
    @synchronize(self){
        如果(sharedInst == nil){
            / * sharedInst在init中设置* /
            [[self alloc] init];
        }
    }
    返回sharedInst;
}

-(id)初始化
{
    如果(sharedInst!= nil){
        [NSException引发:NSInternalInconsistencyException
            格式:@“无法调用[%@%@];请改用+ [%@%@]”],
            NSStringFromClass([self class]),NSStringFromSelector(_cmd), 
            NSStringFromClass([self class]),
            NSStringFromSelector(@selector(sharedInstance)“];
    } else if(self = [super init]){
        sharedInst =自我;
        / *此处特定的类* /
    }
    返回sharedInst;
}

/ *这些可能不起作用
   GC应用程序。保持单身
   作为一个实际的单例
   非CG应用
* /
-(NSUInteger)retainCount
{
    返回NSUIntegerMax;
}

-(单向无效)释放
{
}

-(id)保留
{
    返回sharedInst;
}

-(id)自动释放
{
    返回sharedInst;
}

3
我注意到如果不将结果分配[[self alloc] init]给sharedInst ,则clang会抱怨泄漏。
pix0r

这样破坏init是IMO的一个非常丑陋的方法。不要搞乱对象的初始化和/或实际创建。如果您改为控制对共享实例的访问点,而不是将单例硬烘烤到对象中,则在编写测试等之后,您将拥有更快乐的时光。硬单例被过度使用。
occulus

12

编辑:此实现已被ARC淘汰。请看看如何实现与ARC兼容的Objective-C单例?以便正确实施。

我在其他答案中读过的所有initialize的实现都有一个共同的错误。

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

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

Apple文档建议您在初始化块中检查类类型。因为子类默认情况下调用初始化。在不明显的情况下,可以通过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
我会生活得更简单,完全避免初始化。
汤姆·安德森


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
+1真的很有趣。我可能会class_replaceMethod用来转化sharedInstance为的克隆simpleSharedInstance。这样,您就不必担心@synchronized再次获得锁。
Dave DeLong 2010年

效果是一样的,使用exchangeImplementations意味着在初始化之后,当您调用sharedInstance时,您实际上是在调用simpleSharedInstance。我实际上是从replaceMethod开始的,但是我决定最好改掉实现,这样如果需要的话,原始的仍然存在……
Kendall Helmstetter Gelner 2010年

在进一步的测试中,我无法使replaceMethod正常工作-在重复调用中,代码仍称为原始sharedInstance而不是simpleSharedInstance。我想可能是因为它们都是类级别的方法...我使用的替换是:class_replaceMethod(self,origSel,method_getImplementation(newMethod),method_getTypeEncoding(newMethod)); 及其一些变化。我可以验证我发布的代码是否有效,并且在第一次通过sharedInstance之后会调用simpleSharedInstance。
Kendall Helmstetter Gelner 2010年

您可以创建一个线程安全的版本,在初始化后无需进行大量运行时处理就不必支付锁定成本,我在下面发布了一个实现。
Louis Gerbarg 2010年

1
+1个好主意。我只是喜欢那些运行时可以做的事情。但是在大多数情况下,这可能是过早的优化。如果我真的必须摆脱同步成本,那么我可能会使用Louis的无锁版本。
斯文(Sven)2010年

6

简短答案:很棒。

长答案:类似...。

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标头以了解发生了什么。在这种情况下,标题注释比文档或手册页更适用。


5

我已经将单例变成一个类,因此其他类可以继承单例属性。

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子类,因此此类确实简化了我的生活,并使代码更简洁。


您可能要使用其他一些锁定机制,因为@synchronized它的速度非常慢,应避免使用。
DarkDust 2012年

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

这不是线程安全的,并且避免在第一次调用后进行昂贵的锁定吗?

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

2
在某些情况下,此处使用的双重检查锁定技术通常是一个实际问题(请参阅aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf或Google对其进行检查)。除非另有说明,否则我认为Objective-C不能幸免。另请参见wincent.com/a/knowledge-base/archives/2006/01/…
史蒂夫·麦森


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. 可细分(第n级)
  2. 兼容ARC
  3. 安全带 allocinit
  4. 懒洋洋的
  5. 线程安全
  6. 无锁(使用+初始化,而不是@synchronize)
  7. 无宏
  8. 无烦恼
  9. 简单

辛格尔顿


1
我正在为您的项目使用NSSingleton,它似乎与KVO不兼容。问题在于,KVO为每个KVO对象创建子类,并为其添加前缀NSKVONotifying_ MyClass。并且它使MyClass + initialize和-init方法被调用两次。
奥列格·特拉克曼

我在最新的Xcode上对此进行了测试,注册或接收KVO事件没有任何麻烦。您可以使用以下代码进行验证:gist.github.com/3065038正如我在Twitter上提到的,+ initialize方法对于NSSingleton调用一次,对于每个子类调用一次。这是Objective-C的属性。
kevinlawler

如果添加NSLog(@"initialize: %@", NSStringFromClass([self class]));+initialize方法,则可以验证类仅初始化一次。
kevinlawler

NSLog(@“ initialize:%@”,NSStringFromClass([self class]));
奥列格·特拉克曼

您可能还希望它与IB兼容。我的是:stackoverflow.com/questions/4609609/...
丹Rosenstark

0

您不想在self上进行同步...因为self对象还不存在!您最终锁定了一个临时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
其余方法,访问器方法,变异器方法等应在自身上同步。所有class(+)方法和初始化程序(可能还有-dealloc)都应在类对象上同步。如果您使用Objective-C 2.0属性而不是访问器/更改器方法,则可以避免手动同步。所有object.property和object.property = foo都会自动同步到self。
罗伯·多森

3
请解释为什么您认为该self对象在类方法中不存在。运行时根据它为self每个方法(类或实例)提供的完全相同的值来确定要调用的方法实现。
dreamlax 2010年

2
类方法的内部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; }
jscs

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
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

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

为什么接口宏几乎为空?头文件和代码文件之间的代码一致性;如果您想添加更多自动方法或对其进行更改,则具有可维护性。

我正在使用initialize方法来创建单例,如此处(在撰写本文时)最受欢迎的答案所使用的那样。


0

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

[[Librarian sharedInstance] openLibrary]

至:

[Librarian openLibrary]

通过将类包装在另一个仅具有Class Methods的类中,这样就不会偶然创建重复的实例,因为我们没有创建任何实例!

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


您的链接不再起作用。
i_am_jorf

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

我的方法很简单,像这样:

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时尚未初始化。


目前尚不清楚这标志着initialized作为volatile就足够了。参见aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
i_am_jorf

0

我没有阅读所有解决方案,因此请谅解如果这段代码是多余的。

我认为这是最线程安全的实现。

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

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

    return sharedResourcesObj;
}

-4

我通常使用与Ben Hoffstein的答案大致相同的代码(我也从Wikipedia中获得了答案)。我使用它的原因是克里斯·汉森(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(etc.)的实现留给读者,尽管上面的代码仅是垃圾收集环境中所需的。


2
您的代码不是线程安全的。它在alloc方法中使用同步,但在init方法中未使用。检查初始化的布尔值不是线程安全的。
Mecki

-5

可接受的答案尽管可以编译,但却是不正确的。

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

根据Apple文档:

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

即使使用自我作品,也不应这样做,这对我来说就像是复制粘贴错误。类工厂方法的正确实现是:

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

6
自我最肯定确实存在它的类范围。它引用类而不是类的实例。类(主要是)是一流的对象。
schwa

为什么将@synchroninzed WITHIN放在一个方法中?
2011年

1
如schwa所说,self 类方法内部的类对象。请参阅我的评论以演示此内容。
jscs

self存在,但将其用作传递给的标识符@synchronized将同步对实例方法的访问。正如@ user490696指出的那样,在某些情况下(例如单例),最好使用class对象。从《 Obj-C编程指南》中:You 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.
平息
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.