如果可以使用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;
}


评论

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

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

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

我知道在这个例子中它是无关紧要的。但是人们为什么不更多地使用“新的”。 dispatch_once(&once,^ {sharedInstance = [self new];}}看起来有点整洁。等效于alloc + init。

确保开始使用返回类型instancetype。使用id代替id时,代码完成要好得多。

#1 楼

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

评论


您如何释放它呢?

– Samvermette
2012年1月10日23:20

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

–戴夫·德隆(Dave DeLong)
2012年1月10日23:34

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

–jacekmigacz
2013年1月25日13:40

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

–凯文
13年8月17日在22:31

@ WalterMartinVargas-Pena静态变量拥有强引用

–戴夫·德隆(Dave DeLong)
13年11月15日,0:42

#2 楼

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


评论


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

–法蒂
13年11月18日在17:32

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

– Galactica
19年10月1日,下午1:22

#3 楼

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


评论


初始化如何不可用?至少不是一次初始化可用吗?

–蜂蜜
16年4月8日在22:27

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

–谢尔盖·佩特鲁克(Sergey Petruk)
16年4月13日在10:57

@ asma22 __attribute __(((unavailable())不能用于这些方法。如果另一个程序员想要使用标记为不可用的使用方法,他会报错

–谢尔盖·佩特鲁克(Sergey Petruk)
16年4月13日在11:17

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

–蜂蜜
16-4-13在11:19

这仅适用于MySingleton,例如在MySingleton.m中,我正在调用[super alloc]

–谢尔盖·佩特鲁克(Sergey Petruk)
16-4-19在7:44



#4 楼

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


评论


这在上面的评论中回答了我的问题。并不是说我非常喜欢防御性编程,而是...

–尼古拉斯·米亚里(Nicolas Miari)
13年11月14日在2:50

#5 楼

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

评论


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

–戴夫·德隆(Dave DeLong)
2011年4月19日在18:33

使用ARC完全无效。

– logancautrell
2011年11月1日,下午1:55

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

–工具熊
15年2月20日在4:33

#6 楼

如果您想确保[[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;分配的结果将被释放。

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

#7 楼

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

一些想法:


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

但是,您问这是否是“最佳”方法。但是,应该承认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看起来将支持子类,但不会。如果您打算支持子类化,则至少要包含一些文档,这些文档会警告未来的开发人员他们必须重写此方法。


为实现与Swift的最佳互操作性,您可能希望将其定义为属性,而不是类方法,例如:

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


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

+ (Foo *)sharedFoo { ... }


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

let foo = Foo.shared


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

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

@property (class, readonly, strong) NSURLSession *sharedSession;



另外一个非常小的Swift互操作性考虑因素是单例的名称。最好是可以合并类型的名称,而不是sharedInstance。例如,如果类为Foo,则可以将singleton属性定义为sharedFoo。或者,如果类是DatabaseManager,则可以调用属性sharedManager。然后,Swift用户可以执行以下操作:

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


很显然,如果您真的想使用sharedInstance,则始终可以在需要时声明Swift名称:
<显然,在编写Objective-C代码时,我们不应该让Swift的互操作性超过其他设计考虑因素,但是,如果我们可以编写能够优雅地支持两种语言的代码,那是更好的选择。

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


#8 楼

要创建线程安全的单例,您可以这样操作:

@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
2014年12月15日上午8:49

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

–vikingosegundo
2014年12月16日上午9:08

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

– Hancock_Xu
2014年12月31日23:36

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

–vikingosegundo
2015年1月1日在3:07



#9 楼

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


#10 楼

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