使用哪种同步方法来确保单例仍然是单例?
+(Foo*)sharedInstance
{
@synchronized(self)
{
if (nil == _sharedInstance)
{
_sharedInstance = [[Foo alloc] init];
...
}
}
return _sharedInstance;
}
还是使用互斥?
#import <pthread.h>
static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
+(Foo*)sharedInstance
{
pthread_mutex_lock(&_mutex);
if (nil == _sharedInstance)
{
_sharedInstance = [[Foo alloc] init];
...
}
pthread_mutex_unlock(&_mutex);
return _sharedInstance;
}
嗯......对此有何评论?
答案 0 :(得分:56)
请务必阅读有关此问题/答案的讨论。 Why should we separate alloc and init calls to avoid deadlocks in Objective-C?
扩大竞争条件问题; 真正的修复程序是在您的应用程序中没有不确定的初始化。 不确定或延迟初始化导致的行为很容易因看似无害的更改而改变 - 配置,“无关”代码更改等...
最好在程序的生命周期中明确初始化已知良好点上的子系统。即如果确实需要在程序的早期初始化该子系统(或者如果您想要更加防御,则将其移动到更早的位置),将[MyClass sharedInstance];
放入您的App委托的applicationDidFinishLaunching:
方法中。
最好还是完全从该方法中移出初始化。即[MyClass initializeSharedInstance];
其中+sharedInstance
断言()如果不首先调用该方法。
尽管我是一个方便的粉丝,25年的ObjC编程告诉我,懒惰的初始化是一个更多的维护和重构头痛的源泉。
虽然存在下面描述的竞争条件,但此代码不会修复下面描述的内容。几十年来我们不担心共享实例初始化器中的并发性。为繁荣留下错误的代码。
请记住,对于科林和哈拉德的正确答案,有一种非常微妙的竞争条件可能会让你陷入悲痛的世界。
即,如果正在分配的类的-init
碰巧调用sharedInstance
方法,它将在设置变量之前执行此操作。在这两种情况下都会导致僵局。
这是您想要分离alloc和init的一次。克服Colin的代码,因为它是最好的解决方案(假设是Mac OS X):
+(MyClass *)sharedInstance
{
static MyClass *sharedInstance = nil;
static dispatch_once_t pred;
// partial fix for the "new" concurrency issue
if (sharedInstance) return sharedInstance;
// partial because it means that +sharedInstance *may* return an un-initialized instance
// this is from https://stackoverflow.com/questions/20895214/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/20895427#20895427
dispatch_once(&pred, ^{
sharedInstance = [MyClass alloc];
sharedInstance = [sharedInstance init];
});
return sharedInstance;
}
注意这仅适用于Mac OS X;特别是X 10.6+和iOS 4.0+。在较旧的操作系统上,如果没有块,请使用锁或其中一种方法,即在不基于块的情况下执行某些操作。
上述模式实际上并没有阻止文本中描述的问题,并且在遇到问题时会导致死锁。问题是,dispatch_once()
不是可重入的,因此init
调用sharedInstance
,楔入城市。
答案 1 :(得分:38)
最快的线程安全方法是使用Grand Central Dispatch(libdispatch)和dispatch_once()
+(MyClass *)sharedInstance
{
static MyClass *sharedInstance = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance = [[MyClass alloc] init];
});
return sharedInstance;
}
答案 2 :(得分:12)
如果有人关心,这里有一个同样的宏:
/*!
* @function Singleton GCD Macro
*/
#ifndef SINGLETON_GCD
#define SINGLETON_GCD(classname) \
\
+ (classname *)shared##classname { \
\
static dispatch_once_t pred; \
static classname * shared##classname = nil; \
dispatch_once( &pred, ^{ \
shared##classname = [[self alloc] init]; \
}); \
return shared##classname; \
}
#endif
答案 3 :(得分:2)
此CocoaDev page可能对您有用。
答案 4 :(得分:1)
如果有人关心,这是同一件事的另一个宏:)
恕我直言,与the other variations相比,它提供了更大的灵活性。
#define SHARED_INSTANCE(...) ({\
static dispatch_once_t pred;\
static id sharedObject;\
dispatch_once(&pred, ^{\
sharedObject = (__VA_ARGS__);\
});\
sharedObject;\
})
用法,单行初始化:
+ (instancetype) sharedInstanceOneLine {
return SHARED_INSTANCE( [[self alloc] init] );
}
用法,多行初始化(注意代码块周围的花括号):
+ (instancetype) sharedInstanceMultiLine {
return SHARED_INSTANCE({
NSLog(@"creating shared instance");
CGFloat someValue = 84 / 2.0f;
[[self alloc] initWithSomeValue:someValue]; // no return statement
});
}
在作业的正确部分使用:
- (void) someMethod {
MethodPrivateHelper *helper = SHARED_INSTANCE( [[MethodPrivateHelper alloc] init] );
// do smth with the helper
}
// someMethod should not call itself to avoid deadlock, see bbum's answer
此修改使用了两种语言功能:GCC compound expressions扩展(Clang也支持)和C99 variadic macros support。
预处理后,输出看起来像(您可以通过在Xcode 5中调用Product > Perform Action > Preprocess "YourClassName.m"
来自行测试):
+ (instancetype) sharedInstanceOneLine {
return ({
static dispatch_once_t pred;
static id sharedObject;
dispatch_once(&pred, ^{
sharedObject = ( [[self alloc] init] );
});
sharedObject; // this object will be returned from the block
});
}
+ (instancetype) sharedInstanceMultiLine {
return ({
static dispatch_once_t pred;
static id sharedObject;
dispatch_once(&pred, ^{
sharedObject = ({
NSLog(@"creating shared instance");
CGFloat someValue = 84 / 2.0f;
[[self alloc] initWithSomeValue:someValue];
});
});
sharedObject;
});
}
- (void) someMethod {
MethodPrivateHelper *helper = ({
static dispatch_once_t pred;
static id sharedObject;
dispatch_once(&pred, ^{
sharedObject = ( [[MethodPrivateHelper alloc] init] );
});
sharedObject;
});
}