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


341

如果您可以定位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;
}

2
有没有一种方法可以防止该类的用户调用alloc / copy?
Nicolas Miari 2012年

3
dispatch_once_t和dispatch_once似乎是在4.0中而不是4.1中引入的(请参阅:developer.apple.com/library/ios/#documentation/Performance/…
Ben Flynn

1
如果init要求使用单例对象,则此方法会出现问题。Matt Gallagher的代码已经为我使用了很多次。 cocoawithlove.com/2008/11/…–
greg

1
我知道在这个例子中它是无关紧要的。但是人们为什么不更多地使用“新的”。dispatch_once(一次,^ {sharedInstance = [自我新];}只是看起来该位整洁这相当于ALLOC +初始化。
克里斯·哈顿

3
确保开始使用返回类型instancetype。使用而不是时,代码完成会好得多id
罗杰斯先生

Answers:


215

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


4
您如何释放它呢?
samvermette 2012年

65
@samvermette你不知道。单身人士的要点是它将永远存在。因此,您不会释放它,并且内存会随着进程退出而回收。
Dave DeLong 2012年

6
@戴夫·德隆(Dave DeLong):我认为拥有单身人士的目的不是确定其永生,而是确定我们有一个实例。如果该单例减少信号量怎么办?您不能随便说它将永远存在。
jacekmigacz 2013年

4
@hooleyhoop是的,在其文档中。“如果从多个线程中同时调用,则此函数将同步等待,直到块完成为止。”
凯文

3
@ WalterMartinVargas-Pena静态变量拥有强大的参考
Dave DeLong

36

实例类型

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

4
很棒的提示,谢谢! instancetype是上下文关键字,可以用作结果类型以表示方法返回相关的结果类型。... 使用instancetype,编译器将正确推断类型。
Fattie

1
我不清楚这两个摘要在这里是什么意思,它们彼此等效吗?一个比另一个更好?如果作者可以为此添加一点说明,那就太好了。
galactica

33

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

初始化如何不可用?至少不是一个人可用init吗?
亲爱的

2
Singleton应该只有一个访问点。这就是sharedInstance。如果我们在* .h文件中有init方法,则可以创建另一个单例实例。这与单例的定义相矛盾。
Sergey Petruk '16

1
@ asma22 __attribute __(((unavailable())不能使用这些方法。如果另一个程序员希望使用标记为不可用的使用方法,他会出错
Sergey Petruk

1
我完全明白了,我很高兴我学到了一些新东西,您的答案没错,对于新手来说可能有点困惑...
亲爱的

1
这仅适用于MySingleton,例如在MySingleton.m我打电话时[super alloc]
Sergey Petruk '16

6

您可以通过覆盖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;
}

1
这在上面的评论中回答了我的问题。并不是说我对防御性编程非常重视,但是……
Nicolas Miari

5

戴夫是正确的,那很好。您可能想查看Apple的创建单例文档,以获取有关实现其他一些方法的提示,以确保如果类选择不使用sharedFoo方法,则只能创建一个。


8
嗯,这不是创建单例的最大例子。不需要覆盖内存管理方法。
戴夫·德隆

19
使用ARC完全无效。
logancautrell

被引用的文档此后已退役。而且,仅链接到外部内容的答案通常是较差的SO答案。至少摘录您的答案中的相关部分。除非您想保留后代的旧方法,否则不要在这里打扰。
工具熊

4

如果要确保[[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的任何调用都将完全忽略返回的alloc并返回相同的sharedInstance;alloc的结果将被释放。

+ sharedInstance方法将一如既往地工作。如果不是第一个调用[[MyClass alloc] init]的调用者,则init的结果不是alloc调用的结果,但是可以。


2

您问这是否是“创建单例的最佳方法”。

一些想法:

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

  2. 但是,您问过,这是否是“最佳”方法。但是,应该承认instancetype[[self alloc] init]与单例一起使用时可能会产生误导。

    这样做的好处instancetype是,这是一种明确的方法,可以声明该类可以被子类化,而无需诉诸于id像去年那样进行使用。

    但是static这种方法提出了子类化的挑战。如果if ImageCacheBlobCachesingleton都是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

    然后,您可以继续为此属性编写一个吸气剂(实现将使用dispatch_once您建议的模式):

    + (Foo *)sharedFoo { ... }

    这样做的好处是,如果Swift用户开始使用它,他们将执行以下操作:

    let foo = Foo.shared

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

    顺便说一句,如果您查看Apple如何定义他们的单身人士,这就是他们采用的模式,例如,他们的NSURLSession单身人士定义如下:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. 另一个非常小的Swift互操作性考虑因素是单例的名称。最好是可以合并类型的名称,而不是sharedInstance。例如,如果类为Foo,则可以将singleton属性定义为sharedFoo。或者如果是该类DatabaseManager,则可以调用该属性sharedManager。然后,Swift用户可以执行以下操作:

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

    显然,如果您确实想使用sharedInstance,则可以随时声明Swift名称:

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

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

  5. 我同意其他人的意见,他们指出,如果您希望这是一个真正的单例,开发人员无法(不应)实例化自己的实例,则unavailable限定符为on initnew谨慎。


0

要创建线程安全单例,您可以执行以下操作:

@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中的单例


您正在链接到一篇非常古老的文章,而OP要求提供有关最新实现的特征。
vikingosegundo

1
问题是关于特定的实现。您只需发布另一个实现。因此,您甚至不必尝试回答问题。
vikingosegundo

1
@vikingosegundo问问天气,GCD是创建Thread安全单例的最佳方法,我的回答还有其他选择。
Hancock_Xu 2014年

询问者询问某个实现是否是线程安全的。他没有要求选择。
vikingosegundo 2015年

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

0
@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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.