在Objective-C中使用GCD's dispatch_once创建单例

如果你的目标系统是iOS 4.0或以上

使用GCD,它是在Objective-C(线程安全)中创建单例的最好方法吗?

+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
97366 次浏览

这是创建类实例的一种完全可接受且线程安全的方法。从技术上讲,它可能不是一个“单对象”(因为只能有一个这样的对象),但只要你只使用[Foo sharedFoo]方法来访问对象,这就足够了。

戴夫说得对,这完全没问题。如果类选择不使用sharedFoo方法,你可能想要查看苹果关于创建单例对象的文档中关于实现其他一些方法的提示,以确保只有一个方法可以被创建。

可以避免通过覆盖alloc方法来分配类。

@implementation MyClass


static BOOL useinside = NO;
static id _sharedObject = nil;




+(id) alloc {
if (!useinside) {
@throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
}
else {
return [super alloc];
}
}


+(id)sharedInstance
{
static dispatch_once_t p = 0;
dispatch_once(&p, ^{
useinside = YES;
_sharedObject = [[MyClass alloc] init];
useinside = NO;
});
// returns the same object each time
return _sharedObject;
}

instancetype

instancetype只是Objective-C的众多语言扩展之一,每个新版本都会添加更多扩展。

了解它,热爱它。

并将其作为一个例子,说明如何关注底层细节可以让您深入了解转换Objective-C的强大新方法。

参考:instancetype


+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;


dispatch_once(&once, ^
{
sharedInstance = [self new];
});
return sharedInstance;
}

+ (Class*)sharedInstance
{
static dispatch_once_t once;
static Class *sharedInstance;


dispatch_once(&once, ^
{
sharedInstance = [self new];
});
return sharedInstance;
}

MySingleton.h

@interface MySingleton : NSObject


+(instancetype)sharedInstance;


+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));


@end

MySingleton.m

@implementation MySingleton


+(instancetype)sharedInstance {
static dispatch_once_t pred;
static id shared = nil;
dispatch_once(&pred, ^{
shared = [[super alloc] initUniqueInstance];
});
return shared;
}


-(instancetype)initUniqueInstance {
return [super init];
}


@end

如果你想确保[[MyClass alloc] init]返回与sharedInstance相同的对象(在我看来没有必要,但有些人想要它),可以使用第二个dispatch_once非常容易和安全地完成:

- (instancetype)init
{
static dispatch_once_t once;
static Class *sharedInstance;


dispatch_once(&once, ^
{
// Your normal init code goes here.
sharedInstance = self;
});


return sharedInstance;
}

这允许[[MyClass alloc] init]和[MyClass sharedInstance]的任何组合返回相同的对象;[MyClass sharedInstance]会更有效一点。它的工作原理:[MyClass sharedInstance]将调用[[MyClass alloc] init]一次。其他代码也可以调用它,任意次数。init的第一个调用者将执行“正常”初始化,并将单例对象存储在init方法中。以后任何对init的调用都将完全忽略alloc返回的内容,并返回相同的sharedInstance;alloc的结果将被释放。

+sharedInstance方法将像往常一样工作。如果它不是第一个调用[[MyClass alloc] init]的调用者,那么init的结果不是alloc调用的结果,但这是可以的。

要创建线程安全单例,你可以这样做:

@interface SomeManager : NSObject
+ (id)sharedManager;
@end


/* thread safe */
@implementation SomeManager


static id sharedManager = nil;


+ (void)initialize {
if (self == [SomeManager class]) {
sharedManager = [[self alloc] init];
}
}


+ (id)sharedManager {
return sharedManager;
}
@end

这个博客解释得很好objc/cocoa中的单例

//Create Singleton
+( instancetype )defaultDBManager
{


static dispatch_once_t onceToken = 0;
__strong static id _sharedObject = nil;


dispatch_once(&onceToken, ^{
_sharedObject = [[self alloc] init];
});


return _sharedObject;
}




//In it method
-(instancetype)init
{
self = [super init];
if(self)
{
//Do your custom initialization
}
return self;
}

你问这是否是“创建单例的最佳方式”。

以下是一些想法:

  1. 首先,这是一个线程安全的解决方案。这个dispatch_once模式是在Objective-C中生成单例对象的现代、线程安全的方式。不用担心。

  2. 不过,你问的是,这是否是“最好”的方式。不过,应该承认,instancetype[[self alloc] init]在与单例对象一起使用时可能会产生误导。

    instancetype的好处是,它是一种明确的方式,可以声明类可以被子类化,而无需求助于id类型,就像我们在去年必须做的那样。

    但是这个方法中的static提出了子类化的挑战。如果ImageCacheBlobCache单例对象都是Cache超类的子类,而没有实现它们自己的sharedCache方法呢?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!
    

    为了实现这一点,你必须确保子类实现自己的sharedInstance(或任何你为特定类调用它的方法)方法。

    底线,你原来的sharedInstance 看起来像它将支持子类,但它不会。如果你打算支持子类化,至少包括文档,警告未来的开发人员他们必须重写这个方法

  3. 为了与Swift的最佳互操作性,你可能想要将其定义为一个属性,而不是一个类方法,例如:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end
    

    然后你可以继续为这个属性写一个getter(实现将使用你建议的dispatch_once模式):

    + (Foo *)sharedFoo { ... }
    

    这样做的好处是,如果Swift用户要使用它,他们会做这样的事情:

    let foo = Foo.shared
    

    注意,这里没有(),因为我们将它实现为一个属性。从Swift 3开始,这就是通常访问单例对象的方式。因此将其定义为属性有助于促进互操作性。

    顺便说一句,如果你看看苹果是如何定义他们的单例对象的,这是他们采用的模式,例如,他们的NSURLSession单例对象的定义如下:

    @property (class, readonly, strong) NSURLSession *sharedSession;
    
  4. Another, very minor Swift interoperability consideration was the name of the singleton. It's best if you can incorporate the name of the type, rather than sharedInstance. For example, if the class was Foo, you might define the singleton property as sharedFoo. Or if the class was DatabaseManager, you might call the property sharedManager. Then Swift users could do:

    let foo = Foo.shared
    let manager = DatabaseManager.shared
    

    显然,如果你真的想使用sharedInstance,你可以随时声明Swift名称:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);
    

    显然,在编写Objective-C代码时,我们不应该让Swift互操作性超过其他设计考虑因素,但是,如果我们能够编写优雅地支持这两种语言的代码,那就更好了

  5. 我同意其他人的观点,如果你想让它成为一个真正的单例,开发人员不能/不应该(意外地)实例化他们自己的实例,在initnew上使用unavailable限定符是谨慎的。

@interface className : NSObject{
+(className*)SingleTonShare;
}


@implementation className


+(className*)SingleTonShare{


static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{


if (sharedObj == nil){
sharedObj = [[className alloc] init];
}
});
return sharedObj;
}