可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to have a thread safe, ARC compatible singleton, but is seems to me that the most common example of singleton that I find, an example pasted here:
+ (MyClass *)sharedInstance
{
static MyClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
doesn't stops other developer from calling [[MyClass alloc] init] and overriding the desired flow.
What is the proper way to handle it (apart from throwing exception in init)?
回答1:
you also have to override the +alloc
method to avoid to allocate more than one instance of the singleton.
EDIT#3: well, I really know what the official documentation says about overriding the +alloc
method, but to achieve the asked benefit there is no way to avoid it. personally I don't agree to do it but it can provide the desired result.
it would be like this:
static MyClass *_sharedInstance = nil;
static BOOL _bypassAllocMethod = TRUE;
+ (id)sharedInstance {
@synchronized([MyClass class]) {
if (_sharedInstance == nil) {
_sharedInstance = [[MyClass alloc] init];
}
}
return _sharedInstance;
}
+ (id)alloc {
@synchronized([MyClass class]) {
_bypassAllocMethod = FALSE; // EDIT #2
if (_sharedInstance == nil) {
_sharedInstance = [super alloc];
return _sharedInstance;
} else {
// EDIT #1 : you could throw an exception here to avoid the double allocation of the singleton class
@throw [NSException exceptionWithName:[NSString stringWithFormat:@"<%@: %p> Double allocation issue", [_sharedInstance class], _sharedInstance] reason:@"You cannot allocate the singeton class twice or more." userInfo:nil];
}
}
return nil;
}
// EDIT #2 : the init method
- (id)init {
if (_bypassAllocMethod)
@throw [NSException exceptionWithName:@"invalid allocation" reason:@"invalid allocation" userInfo:nil];
if (self = [super init]) {
}
return self
}
EDIT #1
you don't definitely need to throw an exception here but it is much more visual feedback for the developers of they use your class in wrong way, than sending back a simple nil
pointer.
EDIT #2
I've added a simple trick to avoid the developers instantiate the class to bypass the modified +alloc
method, in that case the allocation will work well but the -init
will throw an exception.
回答2:
Use the Borg pattern instead of the Singleton pattern: Allow multiple instantiation of your class and have the instances share the same static state.
// Shared data
static NSDictionary *sharedData = nil;
+ (void) initialize {
// Initialize shared data
sharedData = [[NSDictionary alloc] init];
}
- (id) init {
self = [super init];
if (self) {
self.data = sharedData;
}
}
This way, clients may arbitrarily use static getInstance
methods or init
methods, and receive objects sharing the same state. They need not even be aware that it's a singleton.
回答3:
I tend to use the following: (with newer instancetype
compiler syntax)
@implementation MyClass
+ (instancetype)myClass {
static MyClass *singleton; // keep global variables in the most minimal scope
if (singleton == nil) @synchronized (self) {
singleton = [[MyClass alloc] initPrivate];
}
return singleton;
}
- (instancetype)initPrivate { // ARC requires the method start with "init…"
self = [super init];
return self;
}
- (instancetype)init {
return nil;
}
@end
This also doesn't prevent others from calling [[MyClass alloc] privateInit]
but it will warn them (unless they write their own hushing code).
This would also leak pre-ARC if someone calls [[MyClass alloc] init]
- but you've got bigger problems if that happens. Optionally, you could throw an exception when init is called. (as in holex's answer)
Also, subclasses could theoretically get in a race condition. If you're worried about that change @synchronized (self)
to @synchronized ([McClass class])
. I prefer the self
as cleaner code and I know there won't be subclasses.
回答4:
convert instance to class method and use the Class object as the singleton.
for example you have a singleton class like this
@interface MySingleton {
int count;
}
+ (MySingleton *)sharedInstance;
- (int)getNext;
@end
I am suggesting you to convert it to
@interface MySingleton
+ (int)getNext;
@end
in MySingleton.m
static int count;
then you can use it like
[MySingleton getNext];
or
id obj = [MySingleton class]; // Class objects are singleton provided by runtime
[obj getNext];
EDIT
I just want to point out, there are already so many ObjC implementation for singleton pattern.
http://www.cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
https://github.com/fadingred/objc-singleton
simple google search will find them. everything are considered. (much more than what I was expected)