1

我想要一个线程安全、与 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

4 回答 4

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会引发异常。

于 2013-02-01T13:05:45.967 回答
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方法,并接收共享相同状态的对象。他们甚至不需要意识到这是一个单例。

于 2013-02-01T13:03:43.940 回答
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],但会警告他们(除非他们编写自己的静默代码)。

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

此外,理论上子类可能会进入竞争状态。如果您担心更改@synchronized (self)@synchronized ([McClass class]). 我更喜欢更简洁的self代码,而且我知道不会有子类。

于 2013-09-26T20:23:28.993 回答
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

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

于 2013-02-01T13:06:11.757 回答