我的单例访问方法通常是以下变体之一:
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
我应该做些什么来改进这个呢?
我的单例访问方法通常是以下变体之一:
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
我应该做些什么来改进这个呢?
另一个选项是使用+(void)initialize
方法。文档提到:
运行时在程序中第一次向类或继承自它的类发送消息之前,会向每个类发送
initialize
方法,确保该方法只被调用一次。(因此,如果不使用该类,则可能永远不会调用该方法。)运行时以线程安全的方式向类发送initialize
消息。超类会在子类之前收到该消息。
因此,你可以做类似于这样的事情:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
+initialize
方法,那么当第一次使用子类时将调用其父类的实现。 - Svenrelease
方法并将其置为空。 :) - user142019@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
@end
MySingleton *s = [[MySingelton alloc] init];
- lindon foxPro Objective-C Design Patterns for iOS
,它详细介绍了如何创建“严格”的单例模式。基本上,由于您无法使初始化方法私有化,因此需要覆盖方法alloc和copy。因此,如果您尝试执行类似[[MySingelton alloc] init]
的操作,您将收到运行时错误(不幸的是不会在编译时出错)。我不理解对象创建的所有细节,但您需要实现+ (id) allocWithZone:(NSZone *)zone
,该方法在sharedSingleton
中被调用。 - lindon fox根据我在下面的另一个回答中所述,我认为您应该执行以下操作:
+ (id)sharedFoo
{
static dispatch_once_t once;
static MyFoo *sharedFoo;
dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
return sharedFoo;
}
自从Kendall发布了一个尝试避免锁定成本的线程安全单例,我也想发表一个:
#import <libkern/OSAtomic.h>
static void * volatile sharedInstance = nil;
+ (className *) sharedInstance {
while (!sharedInstance) {
className *temp = [[self alloc] init];
if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
[temp release];
}
}
return sharedInstance;
}
好的,让我解释一下这是如何工作的:
快速情况:在正常执行中,sharedInstance
已经被设置,因此 while
循环从未执行并且该函数在仅测试变量是否存在后返回;
慢速情况:如果 sharedInstance
不存在,则会分配一个实例并将其复制到其中,使用比较和交换('CAS');
争用情况:如果两个线程同时尝试调用 sharedInstance
且在同一时间 AND sharedInstance
不存在,则它们都将初始化单例的新实例并尝试将其 CAS 到位置。无论哪个线程赢得了 CAS 都会立即返回,输的线程则会释放它刚刚分配的实例并返回现在已设置的 sharedInstance
。单个的 OSAtomicCompareAndSwapPtrBarrier
同时充当了写障碍和读障碍。
init
方法应该做什么?我认为在初始化sharedInstance
时抛出异常并不是一个好主意。那么要怎么做才能防止用户直接多次调用init
呢? - matmvolatile
修饰符呢?由于 sharedInstance 只被初始化一次,我们如何通过使用 volatile
防止编译器将其缓存到寄存器中? - Tony[[self alloc] init]
的结果分配给sharedInst
,那么Clang会报告一个泄漏(leak)。 - pix0r编辑:这个实现方式已经被 ARC 废弃了。请查看如何实现一个与 ARC 兼容的 Objective-C 单例?获取正确的实现。
我在其他答案中阅读到的所有 initialize 实现都有一个共同的错误。
+ (void) initialize {
_instance = [[MySingletonClass alloc] init] // <----- Wrong!
}
+ (void) initialize {
if (self == [MySingletonClass class]){ // <----- Correct!
_instance = [[MySingletonClass alloc] init]
}
}
苹果文档建议您在初始化块中检查类类型,因为子类默认会调用初始化。存在一种不明显的情况,即子类可能通过KVO间接创建。例如,如果您在另一个类中添加以下行:
[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Objective-C会隐式地创建一个MySingletonClass的子类,从而导致第二次触发+initialize
方法。
你可能认为应该在你的初始化块中隐式检查重复初始化,代码如下:
- (id) init { <----- Wrong!
if (_instance != nil) {
// Some hack
}
else {
// Do stuff
}
return self;
}
但你会自食其果;或者更糟糕的是给其他开发人员自食其果的机会。
- (id) init { <----- Correct!
NSAssert(_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self){
// Do stuff
}
return self;
}
简而言之,这是我的实现
@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
if (self == [MySingletonClass class]){
_instance = [[MySingletonClass alloc] init];
}
}
- (id) init {
ZAssert (_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self) {
// Initialization
}
return self;
}
+ (id) getInstance {
return _instance;
}
@end
(用我们自己的断言宏替换ZAssert;或者直接使用NSAssert。)
关于Singleton宏代码的详细解释可以在Cocoa With Love博客中找到。
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html.
我有一个有趣的sharedInstance变体,它是线程安全的,但在初始化后不会加锁。我还不确定是否足够可靠来修改顶部答案,但我提供它供进一步讨论:
// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;
// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
return (MySingleton *)sharedInstance;
}
+ (MySingleton*)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[MySingleton alloc] init];
// Replace expensive thread-safe method
// with the simpler one that just returns the allocated instance.
SEL origSel = @selector(sharedInstance);
SEL newSel = @selector(simpleSharedInstance);
Method origMethod = class_getClassMethod(self, origSel);
Method newMethod = class_getClassMethod(self, newSel);
method_exchangeImplementations(origMethod, newMethod);
}
}
return (MySingleton *)sharedInstance;
}
class_replaceMethod
将 sharedInstance
转换为 simpleSharedInstance
的克隆体。这样,你就再也不用担心获取 @synchronized
锁了。 - Dave DeLong简短回答:非常棒。
长话短说:大概是这样的......
static SomeSingleton *instance = NULL;
@implementation SomeSingleton
+ (id) instance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == NULL){
instance = [[super allocWithZone:NULL] init];
}
});
return instance;
}
+ (id) allocWithZone:(NSZone *)paramZone {
return [[self instance] retain];
}
- (id) copyWithZone:(NSZone *)paramZone {
return self;
}
- (id) autorelease {
return self;
}
- (NSUInteger) retainCount {
return NSUIntegerMax;
}
- (id) retain {
return self;
}
@end
static id sharedInstance = nil;
#define DEFINE_SHARED_INSTANCE + (id) sharedInstance { return [self sharedInstance:&sharedInstance]; } \
+ (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }
@interface Singleton : NSObject {
}
+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;
+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;
@end
#import "Singleton.h"
@implementation Singleton
+ (id) sharedInstance {
return [self sharedInstance:&sharedInstance];
}
+ (id) sharedInstance:(id*)inst {
@synchronized(self)
{
if (*inst == nil)
*inst = [[self alloc] init];
}
return *inst;
}
+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
@synchronized(self) {
if (*inst == nil) {
*inst = [super allocWithZone:zone];
return *inst; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
@end
#import "Singleton.h"
@interface SomeClass : Singleton {
}
@end
@implementation SomeClass
DEFINE_SHARED_INSTANCE;
@end
@synchronized
非常缓慢并且应该避免使用。 - DarkDust