我的Objective-C单身人物应该是什么样的?

sch*_*hwa 334 singleton design-patterns objective-c object-initializers

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

static MyClass *gInstance = NULL;

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

    return(gInstance);
}
Run Code Online (Sandbox Code Playgroud)

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

Rob*_*son 207

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

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

所以你可以做类似于此的事情:

static MySingleton *sharedSingleton;

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

  • 这也是****因为可能有子类.如果它们不重写`+ initialize`,则在首次使用子类时将调用它们的超类实现. (33认同)
  • 如果运行时只调用一次,那么BOOL会做什么?这是一个预防措施,以防有人从他们的代码中明确调用此函数? (7认同)
  • 是的,这是一个预防措施,因为该功能也可以直接调用. (5认同)
  • @aryaxt:从列出的文档中,这已经是线程安全的.因此,每个运行时调用一次 - 句点.这似乎是正确的,线程安全的,最佳效率的解决方案. (4认同)
  • @Paul你可以覆盖`release`方法并使其为空.:) (3认同)
  • 那么[在文档中创建一个单例实例](http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html#//apple_ref/doc/uid/TP40002974- CH4-SW32)? (3认同)
  • @JJD:Apple的例子是创建透明严格的单例,这几乎是不合适的.大多数单身人士是为了方便,而不是要求(可能不止一个,通常没有;例如NSNotificationCenter).对于*必须*是单例的类,创建多个实例通常是一个编程错误,你应该在`init`中断言而不是"修复"调用者的错误.Apple的例子唯一有意义的时候是单例是一个调用者可以忽略的实现细节.该类至少应该是不可变的. (2认同)

Ben*_*ein 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
Run Code Online (Sandbox Code Playgroud)

[资源]

  • 这是你通常应该用于单身人士的所有内容.除其他外,保持您的类可单独实例化使它们更容易测试,因为您可以测试单独的实例,而不是有办法重置其状态. (7认同)
  • Stig Brautaset:不,在这个例子中省略@synchronized是不可行的.它可以同时处理执行此静态函数的两个线程的可能竞争条件,两个线程同时通过"if(!sharedSingleton)"测试,从而产生两个[MySingleton alloc]. .. @synchronized {scope block}强制假设的第二个线程在被允许进入它之前等待第一个线程退出{scope block}.我希望这有帮助!=) (3认同)
  • 什么阻止某人仍然制作自己的对象实例?`MySingleton*s = [[MySingelton alloc] init];` (3认同)

Col*_*ett 59

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

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

  • 不要打扰你上面所做的一切.让你的(希望极少)单身人士可以单独实例化,并且只有一个共享/默认方法.只有你真的,真的,只需要你的班级的单个实例,你才能做到这一点.你没有,尤其是 用于单元测试. (6认同)

Lou*_*arg 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;                                                                        
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 快速情况:在正常执行sharedInstance中已经设置好了,所以while循环永远不会执行,只需测试变量的存在就会返回函数;

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

  3. 争鸣情况:如果两个线程都试图调用sharedInstance在同一时间 sharedInstance同一时间不存在,那么他们都将初始化为CAS是单身主义者和尝试的新实例到位.无论哪一个赢得CAS立即返回,无论哪一个失去释放它刚刚分配的实例并返回(现在设置)sharedInstance.单个OSAtomicCompareAndSwapPtrBarrier作为设置线程的写屏障和来自测试线程的读屏障.

  • 这在应用程序的生命周期中最多只能发生一次.然而,它是正确的,并且比较和交换技术是一个有用的工具,所以+1. (18认同)
  • 我一般不会阻止它.通常有正当理由允许通常单身实例乘以实例化,最常见的是用于某些类型的单元测试.如果我真的想强制执行单个实例,我可能会检查init方法是否存在全局,如果确实存在,我会释放self并返回全局. (2认同)

小智 14

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

  • 我注意到如果你没有将`[[self alloc] init]`的结果分配给sharedInst,clang会抱怨泄漏. (3认同)

lor*_*ean 12

编辑:此实现已被ARC废弃.请看一下如何实现与ARC兼容的Objective-C单例?为了正确实施.

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

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

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

Apple文档建议您检查初始化块中的类类型.因为子类默认调用initialize.存在一种非显而易见的情况,其中可以通过KVO间接地创建子类.如果您在另一个类中添加以下行:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Run Code Online (Sandbox Code Playgroud)

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

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

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

但是你会用脚射击自己; 或者更糟糕的是让另一个开发者有机会射击自己.

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

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
Run Code Online (Sandbox Code Playgroud)

(用我们自己的断言宏替换ZAssert;或者只用NSAssert.)


Mat*_*ier 10

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

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


Ken*_*ner 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;
}
Run Code Online (Sandbox Code Playgroud)


que*_*ish 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
Run Code Online (Sandbox Code Playgroud)

请务必阅读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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这里有一个类的例子,你想成为单身人士.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end
Run Code Online (Sandbox Code Playgroud)

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