使用GCD,这是在Objective-C(线程安全)中创建单例的最佳方法吗?
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
#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 楼
instancetypeinstancetype
只是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
提出了子类化挑战。如果ImageCache
和BlobCache
单例都是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的互操作性超过其他设计考虑因素,但是,如果我们可以编写能够优雅地支持两种语言的代码,那是更好的选择。
我同意其他人的看法,他们指出,如果您希望这是一个真正的单例,开发人员无法(不应)实例化自己的实例,则
unavailable
和init
上的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;
}
评论
有没有一种方法可以防止该类的用户调用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时,代码完成要好得多。