【问题标题】:What should my Objective-C singleton look like? [closed]我的 Objective-C 单例应该是什么样的? [关闭]
【发布时间】:2010-09-13 19:04:53
【问题描述】:

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

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

【问题讨论】:

  • 你所拥有的很好,尽管你可以将全局变量声明移动到你的 +instance 方法中(唯一需要使用它的地方,除非你也允许它被设置)和为您的方法使用像 +defaultMyClass 或 +sharedMyClass 这样的名称。 +instance 没有意图揭示。
  • 由于这个问题的“答案”不太可能很快改变,我将历史锁定在这个问题上。两个原因 1) 很多观点、投票和好的内容 2) 防止开/关的溜溜球。这在当时是一个很好的问题,但这些类型的问题不适合 Stack Overflow。我们现在有Code Review 用于检查工作代码。请将此问题的所有讨论发送至this meta question

标签: objective-c design-patterns singleton object-initializers


【解决方案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

[Source]

【讨论】:

  • 这就是你通常应该用于单例的所有内容。除此之外,让您的类单独实例化使它们更易于测试,因为您可以测试单独的实例而不是重置它们的状态。
  • Stig Brautaset:不,在此示例中省略 @synchronized 是不行的。它可以处理同时执行此静态函数的两个线程可能的竞争条件,同时通过“if(!sharedSingleton)”测试,从而产生两个 [MySingleton alloc]。 .. @synchronized {scope 块} 强制假设的第二个线程等待第一个线程退出 {scope 块},然后才能继续进入它。我希望这有帮助! =)
  • 是什么阻止了人们仍然制作自己的对象实例? MySingleton *s = [[MySingelton alloc] init];
  • @lindonfox 你的问题的答案是什么?
  • @Raffi - 抱歉,我想我一定忘了粘贴我的答案。无论如何,我得到了Pro Objective-C Design Patterns for iOS 这本书,它详细说明了你如何制作一个“严格”的单曲。基本上,由于您不能将启动方法设为私有,因此您需要覆盖方法分配和复制。因此,如果您尝试执行[[MySingelton alloc] init] 之类的操作,您将收到运行时错误(但不幸的是不是编译时错误)。我不明白对象创建的所有细节,但是你实现了+ (id) allocWithZone:(NSZone *)zone,它在sharedSingleton中被调用
【解决方案2】:
静态 MyClass *sharedInst = nil; + (id)sharedInstance { @同步(自我){ if ( sharedInst == nil ) { /* sharedInst 在 init 中设置 */ [[自分配] 初始化]; } } 返回sharedInst; } - (id)初始化 { 如果(sharedInst!= nil){ [NSException 引发:NSInternalInconsistencyException format:@"[%@ %@] 不能被调用;使用 +[%@ %@] 代替"], NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class]), NSStringFromSelector(@selector(sharedInstance)"]; } else if ( self = [super init] ) { sharedInst = 自我; /* 这里具体的类 */ } 返回sharedInst; } /* 这些可能在 GC 应用程序。保持单身 作为一个实际的单身人士 非CG应用 */ - (NSUInteger)retainCount { 返回 NSUIntegerMax; } - (oneway void)release { } - (id)保留 { 返回sharedInst; } - (id)自动释放 { 返回sharedInst; }

【讨论】:

  • 我注意到如果您不将 [[self alloc] init] 的结果分配给 sharedInst,clang 会抱怨泄漏。
  • 像这样颠覆 init 是一种非常丑陋的方法 IMO。不要混淆 init 和/或对象的实际创建。如果您转而使用对共享实例的受控访问点,而不是将单例硬烘焙到对象中,那么如果编写测试等,您将拥有更快乐的时光。硬单例被过度使用。
【解决方案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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 不要为你在上面做的所有事情而烦恼。使您的(希望极少数)单例可以单独实例化,并且只有一个共享/默认方法。只有当你真的,真的,只想要你的类的一个实例时,你所做的才是必要的。你不知道,尤其是。用于单元测试。
    • 问题是这是“创建单例”的 Apple 示例代码。但是,是的,你是绝对正确的。
    • 如果你想要一个“真正的”单例(即一个只能实例化一次的对象,永远),Apple 示例代码是正确的,但正如 Chris 所说,这很少是你想要或需要的,而有些一种可设置的共享实例是您通常想要的。
    • 这是上述方法的宏:gist.github.com/1057420。这是我用的。
    • 除了单元测试之外,没有人反对这个解决方案,对吗?而且它又快又安全。
    【解决方案5】:

    另一种选择是使用+(void)initialize 方法。来自文档:

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

    所以你可以做类似这样的事情:

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

    【讨论】:

    • 如果运行时只会调用一次,那么 BOOL 会做什么?如果有人从他们的代码中显式调用此函数,这是一种预防措施吗?
    • 是的,这是一个预防措施,因为该函数也可以直接调用。
    • 这也是必需的,因为可能有子类。如果它们不覆盖+initialize,则如果第一次使用子类,则将调用它们的超类实现。
    • @Paul 你可以覆盖release 方法并使其为空。 :)
    • @aryaxt:从列出的文档来看,这已经是线程安全的。因此,每个运行时调用一次 - 周期。这似乎是正确的、线程安全的、最有效的解决方案。
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

      我通常使用与 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(等)的实现留给读者,尽管在垃圾收集环境中您只需要上面的代码。

      【讨论】:

      • 您的代码不是线程安全的。它在 alloc 方法中使用了同步,但在 init 方法中没有。检查初始化的 bool 不是线程安全的。
      【解决方案8】:

      你不想在 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
      

      【讨论】:

      • 其余的方法、访问器方法、修改器方法等应该在自身上同步。所有类(+)方法和初始化程序(可能还有 -dealloc)都应该在类对象上同步。如果您使用 Objective-C 2.0 属性而不是访问器/修改器方法,则可以避免手动同步。所有object.property和object.property = foo,都会自动同步到self。
      • 请解释为什么您认为self 对象不存在于类方法中。运行时根据它为每个方法(类或实例)提供的与self 完全相同的值来确定要调用的方法实现。
      • 在类方法内部,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】:

      我对 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 这真的很有趣。我可能会使用class_replaceMethodsharedInstance 转换为simpleSharedInstance 的克隆。这样您就不必担心再次获得@synchronized 锁。
      • 是一样的效果,使用exchangeImplementations的意思是在init之后调用sharedInstance的时候,其实是在调用simpleSharedInstance。我实际上是从 replaceMethod 开始的,但我决定最好只是切换实现,这样如果需要,原来的仍然存在......
      • 在进一步的测试中,我无法让replaceMethod 工作——在重复调用中,代码仍然调用了原来的sharedInstance 而不是simpleSharedInstance。我认为可能是因为它们都是类级别的方法......我使用的替换是:class_replaceMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));及其一些变体。我可以验证我发布的代码是否有效,并且在第一次通过 sharedInstance 后会调用 simpleSharedInstance。
      • 你可以制作一个线程安全的版本,在初始化后不支付锁定成本,而不需要做一堆运行时弄脏,我在下面发布了一个实现。
      • +1 好主意。我只是喜欢使用运行时可以做的那些事情。但在大多数情况下,这可能是过早的优化。如果我真的必须摆脱同步成本,我可能会使用 Louis 的无锁版本。
      【解决方案10】:

      【讨论】:

      • 链接似乎已损坏 - 我在哪里可以获得该来源?
      【解决方案11】:

      由于Kendall posted 是一个线程安全的单例,它试图避免锁定成本,我想我也会扔一个:

      #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. Fast case:在正常执行中sharedInstance已经被设置,所以while循环永远不会被执行并且函数在简单地测试变量是否存在后返回;

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

      3. 竞争案例:如果两个线程都尝试同时调用sharedInstanceANDsharedInstance 不存在,那么它们都将初始化单例并尝试将其 CAS 放置到位。无论哪一个获胜,CAS 都会立即返回,无论哪一个失败,都会释放它刚刚分配的实例并返回(现在设置的)sharedInstance。单个OSAtomicCompareAndSwapPtrBarrier 既充当设置线程的写入屏障,又充当测试线程的读取屏障。

      【讨论】:

      • 这完全是矫枉过正,在应用程序的生命周期中最多只能发生一次。尽管如此,它是正确的,比较和交换技术是一个有用的工具,所以+1。
      • 不错的答案 - OSAtomic 家族值得了解
      • @Louis:惊人的,非常有启发性的答案!但是有一个问题:我的init 方法在您的方法中应该做什么?我相信,在初始化 sharedInstance 时抛出异常不是一个好主意。那么如何防止用户多次直接调用init呢?
      • 我一般不会阻止。通常有正当的理由允许通常是单例的东西被多次实例化,最常见的是用于某些类型的单元测试。如果我真的想强制执行单个实例,我可能会让 init 方法检查全局是否存在,如果存在,我让它释放自我并返回全局。
      • @Tony 响应有点晚,但 OSAtomicCompareAndSwapPtrBarrier 需要一个 volatile。也许 volatile 关键字是为了防止编译器优化检查?请参阅:stackoverflow.com/a/5334727/449161developer.apple.com/library/mac/#documentation/Darwin/Reference/…
      【解决方案12】:

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

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

      【讨论】:

      【解决方案13】:

      要深入讨论 Objective-C 中的单例模式,请看这里:

      Using the Singleton Pattern in Objective-C

      【讨论】:

        【解决方案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
        

        【讨论】:

          【解决方案15】:

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

          单例.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
          

          单例.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 的子类,所以这个类真的让我的生活变得轻松,让代码更干净。

          【讨论】:

          【解决方案16】:

          只是想把它留在这里,以免丢失。这个的优点是它可以在 InterfaceBuilder 中使用,这是一个巨大的优势。 This is taken from another question that I asked:

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

          【讨论】:

            【解决方案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 工作,它也不应该,这对我来说似乎是一个复制和粘贴错误。 类工厂方法的正确实现是:

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

            【讨论】:

            • self 最肯定 确实 存在它的类范围。它指的是类而不是类的实例。类(大部分)是一流的对象。
            • 为什么要把@synchroninzed 放在一个方法中?
            • 正如 schwa 已经说过的,self 类方法中的类对象。 See my comment 的 sn-p 演示了这一点。
            • self 存在,但使用它作为传递给@synchronized 的标识符将同步访问实例的方法。正如@user490696 指出的那样,在某些情况下(例如单例),使用类对象更可取。来自 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.
            【解决方案18】:

            编辑:此实现已被 ARC 淘汰。请查看How do I implement an Objective-C singleton that is compatible with ARC? 以正确实施。

            我在其他答案中阅读的所有初始化实现都有一个常见错误。

            + (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。)

            【讨论】:

            • 我只想简单一点,完全避免初始化。
            【解决方案19】:

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

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

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

            我正在使用初始化方法来创建单例,这在此处(撰写本文时)最流行的答案中使用。

            【讨论】:

              【解决方案20】:

              怎么样

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

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

              【讨论】:

              • 查看其他答案中关于双重检查锁定的讨论。
              【解决方案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 header 以了解发生了什么。在这种情况下,标题 cmets 比文档或手册页更适用。

              【讨论】:

                【解决方案22】:

                使用 Objective C 类方法,我们可以避免像往常一样使用单例模式,来自:

                [[Librarian sharedInstance] openLibrary]
                

                到:

                [Librarian openLibrary]
                

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

                我写了一篇更详细的博客here:)

                【讨论】:

                • 您的链接不再起作用。
                【解决方案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;
                }
                

                【讨论】:

                  【解决方案24】:

                  KLSingleton 是:

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

                  KLSingleton

                  【讨论】:

                  • 我正在为我的项目使用你的 NSSingleton,它似乎与 KVO 不兼容。问题是 KVO 为每个 KVO 对象创建子类,并为其添加前缀 NSKVONotifying_MyClass。它使 MyClass +initialize 和 -init 方法被调用两次。
                  • 我在最新的 Xcode 上对此进行了测试,注册或接收 KVO 事件没有任何问题。您可以使用以下代码验证这一点:gist.github.com/3065038 正如我在 Twitter 上提到的,+initialize 方法对 NSSingleton 调用一次,对每个子类调用一次。这是 Objective-C 的一个属性。
                  • 如果将NSLog(@"initialize: %@", NSStringFromClass([self class])); 添加到+initialize 方法中,您可以验证这些类是否只初始化一次。
                  • NSLog(@"initialize: %@", NSStringFromClass([self class]));
                  • 您可能还希望它与 IB 兼容。我的是:stackoverflow.com/questions/4609609/…
                  【解决方案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 时它还没有初始化。

                  【讨论】:

                  【解决方案26】:

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

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

                  +(SingletonObject *) sharedManager
                  {
                      static SingletonObject * sharedResourcesObj = nil;
                  
                      @synchronized(self)
                      {
                          if (!sharedResourcesObj)
                          {
                              sharedResourcesObj = [[SingletonObject alloc] init];
                          }
                      }
                  
                      return sharedResourcesObj;
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-07-29
                    • 1970-01-01
                    • 2015-12-09
                    • 2017-12-14
                    • 1970-01-01
                    相关资源
                    最近更新 更多