我的Objective-C单例应该是什么样子?

我的单例访问方法通常是一些变种:

static MyClass *gInstance = NULL;


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


return(gInstance);
}

我能做些什么来改善这一点呢?

184632 次浏览
@interface MySingleton : NSObject
{
}


+ (MySingleton *)sharedSingleton;
@end


@implementation MySingleton


+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;


@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];


return sharedSingleton;
}
}


@end

[来源]

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

这也适用于非垃圾收集环境。

@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

根据我下面的另一个回答,我认为你应该这样做:

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

另一个选项是使用+(void)initialize方法。从文档中可以看到:

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

所以你可以这样做:

static MySingleton *sharedSingleton;


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

关于单例宏代码的详细解释在Cocoa With Love博客上

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

我通常使用的代码大致类似于Ben Hoffstein的回答(这也是我从维基百科上得到的)。我使用它的原因是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(等等)的实现留给读者,尽管以上代码是在垃圾收集环境中所需要的全部代码。

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

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

由于肯德尔发布是一个线程安全的单例,试图避免锁定成本,我想我也会抛出一个:

#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 AND sharedInstance同时不存在,那么它们都将初始化该单例对象的新实例,并尝试将其CAS到相应的位置。无论哪个赢了CAS都会立即返回,无论哪个输了,都会释放它刚刚分配的实例并返回(现在已设置)sharedInstance。单个OSAtomicCompareAndSwapPtrBarrier既充当设置线程的写屏障,也充当测试线程的读屏障。

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

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

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

在Objective-C中使用单例模式

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

我已经将单例转换为一个类,这样其他类就可以继承单例属性。

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

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

单例类唯一的限制是它是NSObject的子类。但大多数时候我在我的代码中使用单例对象,它们实际上是NSObject的子类,所以这个类真的简化了我的生活,使代码更干净。

我只是想把这个放在这里,这样我就不会弄丢了。这个的优点是它可以在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;
}

公认的答案,虽然它编译,是不正确的。

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

根据苹果文档:

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

即使使用self工作,它不应该,这看起来像一个复制和粘贴错误对我来说。 类工厂方法的正确实现应该是:

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

编辑:这个实现在ARC中过时了。请看看我如何实现一个与ARC兼容的Objective-C单例?为正确的实现。

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

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


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

苹果文档建议你检查初始化块中的类类型。因为子类默认调用初始化。存在一种不明显的情况,即可以通过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。)

我知道有很多关于这个“问题”的评论,但是我没有看到很多人建议使用宏来定义单例对象。这是一个非常常见的模式,一个宏极大地简化了单例。

下面是我根据我见过的几个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方法创建单例,就像这里(在撰写本文时)最流行的答案中使用的那样。

如何

static MyClass *gInstance = NULL;


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


return(gInstance);
}

这样就避免了初始化后的同步成本?

简单回答:太棒了。

长话短说:类似....

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

一定要阅读调度/ once.h头来理解发生了什么。在这种情况下,标题注释比文档或手册页更适用。

对于Objective C类方法,我们可以避免以通常的方式使用单例模式,如下所示:

[[Librarian sharedInstance] openLibrary]

:

[Librarian openLibrary]

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

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

扩展一下@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;
}

KLSingleton是:

  1. 可子类化(到n阶)
  2. 弧相容
  3. 使用allocinit安全
  4. 装载
  5. 线程安全的
  6. 无锁(使用+initialize,而不是@synchronize)
  7. Macro-free
  8. Swizzle-free
  9. 简单的

KLSingleton

我的方法很简单:

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时它还没有初始化。

我没有读完所有的解,所以如果这段代码是多余的,请原谅。

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

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


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


return sharedResourcesObj;
}