Objective-C中的Singleton,兼容ARC和线程安全

时间:2013-02-01 12:43:53

标签: objective-c singleton automatic-ref-counting

我想拥有一个线程安全的,ARC兼容的单例,但在我看来,我找到了最常见的单例示例,这里粘贴了一个例子:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

不会阻止其他开发人员调用[[MyClass alloc] init]并覆盖所需的流。 处理它的正确方法是什么(除了在init中抛出异常)?

4 个答案:

答案 0 :(得分:2)

使用Borg模式而不是Singleton模式:允许对类进行多次实例化,并让实例共享相同的静态。

// Shared data
static NSDictionary *sharedData = nil;

+ (void) initialize {
  // Initialize shared data
  sharedData = [[NSDictionary alloc] init];
}

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

  if (self) {
    self.data = sharedData;
  }
}

这样,客户端可以任意使用静态getInstance方法或init方法,并接收共享相同状态的对象。他们甚至不需要意识到它是一个单身人士。

答案 1 :(得分:2)

您还必须覆盖+alloc方法,以避免分配多个单例实例。

编辑#3: 好吧,我真的知道官方文档中有关覆盖 +alloc 方法的内容,但为了实现所要求的利益,没有办法避免它。我个人不同意这样做,但它可以提供所需的结果。

就像这样:

static MyClass *_sharedInstance = nil;
static BOOL _bypassAllocMethod = TRUE;

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

+ (id)alloc {
    @synchronized([MyClass class]) {
         _bypassAllocMethod = FALSE; // EDIT #2
         if (_sharedInstance == nil) {
              _sharedInstance = [super alloc];
              return _sharedInstance;
         } else {
              // EDIT #1 : you could throw an exception here to avoid the double allocation of the singleton class
              @throw [NSException exceptionWithName:[NSString stringWithFormat:@"<%@: %p> Double allocation issue", [_sharedInstance class], _sharedInstance] reason:@"You cannot allocate the singeton class twice or more." userInfo:nil];
         }
    }
    return nil;
}

// EDIT #2 : the init method
- (id)init {
    if (_bypassAllocMethod)
        @throw [NSException exceptionWithName:@"invalid allocation" reason:@"invalid allocation" userInfo:nil];

    if (self = [super init]) {
    }

    return self
}

编辑#1

你不一定需要在这里抛出一个异常,但对于他们以错误的方式使用你的类的开发人员来说,这比发回一个简单的nil指针更为直观。

编辑#2

我添加了一个简单的技巧,以避免开发人员实例化该类以绕过修改后的+alloc方法,在这种情况下,分配将运行良好,但-init将抛出异常。

答案 2 :(得分:1)

我倾向于使用以下内容:(使用较新的instancetype编译器语法)

@implementation MyClass

+ (instancetype)myClass {
  static MyClass *singleton; // keep global variables in the most minimal scope

  if (singleton == nil) @synchronized (self) {
    singleton = [[MyClass alloc] initPrivate];
  }

  return singleton;
}

- (instancetype)initPrivate { // ARC requires the method start with "init…"
  self = [super init];

  return self;
}

- (instancetype)init {
  return nil;
}

@end

这也不会阻止其他人调用[[MyClass alloc] privateInit],但会警告他们(除非他们自己编写代码)。

如果有人打电话给[[MyClass alloc] init],这也会在ARC之前泄漏 - 但如果发生这种情况,你会遇到更大的问题。或者,您可以在调用init时抛出异常。 (如holex的回答)

此外,子类理论上可以处于竞争状态。如果您担心此更改@synchronized (self)@synchronized ([McClass class])。我更喜欢self作为更清晰的代码,我知道不会有子类。

答案 3 :(得分:0)

将实例转换为类方法,并使用Class对象作为单例。

例如你有一个像这样的单身类

@interface MySingleton {
    int count;
}

+ (MySingleton *)sharedInstance;
- (int)getNext;

@end

我建议您将其转换为

@interface MySingleton

+ (int)getNext;

@end

在MySingleton.m

static int count;

然后你可以像

一样使用它
[MySingleton getNext];

id obj = [MySingleton class]; // Class objects are singleton provided by runtime
[obj getNext];

修改

我只想指出,单例模式已经有很多ObjC实现。

http://www.cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html https://github.com/fadingred/objc-singleton

简单的谷歌搜索会找到它们。一切都考虑在内。 (远远超出我的预期)