提问人:schwa 提问时间:9/28/2008 最后编辑:Rachelschwa 更新时间:3/12/2013 访问量:184999
我的 Objective-C 单例应该是什么样子的?[关闭]
What should my Objective-C singleton look like? [closed]
问:
我的单例访问器方法通常是以下的某种变体:
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
我能做些什么来改善这一点?
答:
@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];
Pro Objective-C Design Patterns for iOS
[[MySingelton alloc] init]
+ (id) allocWithZone:(NSZone *)zone
sharedSingleton
static MyClass *sharedInst = nil; + (id)sharedInstance { @synchronize( self ) { if ( sharedInst == nil ) { /* sharedInst set up in init */ [[self alloc] init]; } } return sharedInst; } - (id)init { if ( sharedInst != nil ) { [NSException raise:NSInternalInconsistencyException format:@"[%@ %@] cannot be called; use +[%@ %@] instead"], NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class]), NSStringFromSelector(@selector(sharedInstance)"]; } else if ( self = [super init] ) { sharedInst = self; /* Whatever class specific here */ } return sharedInst; } /* These probably do nothing in a GC app. Keeps singleton as an actual singleton in a non CG app */ - (NSUInteger)retainCount { return NSUIntegerMax; } - (oneway void)release { } - (id)retain { return sharedInst; } - (id)autorelease { return sharedInst; }
评论
[[self alloc] init]
这也适用于非垃圾回收环境。
@interface MySingleton : NSObject {
}
+(MySingleton *)sharedManager;
@end
@implementation MySingleton
static MySingleton *sharedMySingleton = nil;
+(MySingleton*)sharedManager {
@synchronized(self) {
if (sharedMySingleton == nil) {
[[self alloc] init]; // assignment not done here
}
}
return sharedMySingleton;
}
+(id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedMySingleton == nil) {
sharedMySingleton = [super allocWithZone:zone];
return sharedMySingleton; // assignment and return on first allocation
}
}
return nil; //on subsequent allocation attempts return nil
}
-(void)dealloc {
[super dealloc];
}
-(id)copyWithZone:(NSZone *)zone {
return self;
}
-(id)retain {
return self;
}
-(unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be release
}
-(void)release {
//do nothing
}
-(id)autorelease {
return self;
}
-(id)init {
self = [super init];
sharedMySingleton = self;
//initialize here
return self;
}
@end
根据我下面的其他答案,我认为你应该这样做:
+ (id)sharedFoo
{
static dispatch_once_t once;
static MyFoo *sharedFoo;
dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
return sharedFoo;
}
评论
另一种选择是使用该方法。从文档中:+(void)initialize
运行时在类或从该类继承的任何类从程序内部发送其第一条消息之前,恰好向程序中的每个类发送一次。(因此,如果不使用该类,则可能永远不会调用该方法。运行时以线程安全的方式将消息发送到类。超类在其子类之前收到此消息。
initialize
initialize
因此,您可以执行类似于以下内容的操作:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
评论
+initialize
release
博客 Cocoa With Love 上有对 Singleton 宏代码的详尽解释
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html。
我通常使用的代码与Ben Hoffstein的答案大致相似(我也是从维基百科上得到的)。我使用它的原因 克里斯·汉森 (Chris Hanson) 在他的评论中陈述。
但是,有时我需要将单例放入 NIB 中,在这种情况下,我使用以下命令:
@implementation Singleton
static Singleton *singleton = nil;
- (id)init {
static BOOL initialized = NO;
if (!initialized) {
self = [super init];
singleton = self;
initialized = YES;
}
return self;
}
+ (id)allocWithZone:(NSZone*)zone {
@synchronized (self) {
if (!singleton)
singleton = [super allocWithZone:zone];
}
return singleton;
}
+ (Singleton*)sharedSingleton {
if (!singleton)
[[Singleton alloc] init];
return singleton;
}
@end
我将(等)的实现留给读者,尽管上述代码是您在垃圾回收环境中所需要的。-retain
评论
你不想在自己身上同步......因为自我对象还不存在!您最终会锁定一个临时 id 值。您希望确保没有其他人可以运行类方法( sharedInstance, alloc, allocWithZone: 等),因此您需要在类对象上同步:
@implementation MYSingleton
static MYSingleton * sharedInstance = nil;
+( id )sharedInstance {
@synchronized( [ MYSingleton class ] ) {
if( sharedInstance == nil )
sharedInstance = [ [ MYSingleton alloc ] init ];
}
return sharedInstance;
}
+( id )allocWithZone:( NSZone * )zone {
@synchronized( [ MYSingleton class ] ) {
if( sharedInstance == nil )
sharedInstance = [ super allocWithZone:zone ];
}
return sharedInstance;
}
-( id )init {
@synchronized( [ MYSingleton class ] ) {
self = [ super init ];
if( self != nil ) {
// Insert initialization code here
}
return self;
}
}
@end
评论
self
self
self
#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
我在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
这是我放在一起的一个宏:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
它基于 Matt Gallagher 在这里的工作,但将实现更改为使用 Google 的 Dave MacLachlan 在此处描述的方法 swizzing。
我欢迎评论/贡献。
评论
由于 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
慢速情况:如果不存在,则使用比较和交换 ('CAS') 分配实例并将其复制到其中;
sharedInstance
争用情况:如果两个线程都尝试同时调用并且同时不存在,那么它们都将初始化单例的新实例并尝试将其 CAS 到位。无论哪个赢了,CAS 都会立即返回,无论哪个输了,都会释放它刚刚分配的实例并返回 (now set) 。单个既充当设置线程的写入屏障,又充当测试线程的读取屏障。
sharedInstance
sharedInstance
sharedInstance
OSAtomicCompareAndSwapPtrBarrier
评论
init
sharedInstance
init
这难道不是线程安全的,并避免了第一次调用后昂贵的锁定吗?
+ (MySingleton*)sharedInstance
{
if (sharedInstance == nil) {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MySingleton alloc] init];
}
}
}
return (MySingleton *)sharedInstance;
}
评论
有关 Objective-C 中单例模式的深入讨论,请查看此处:
static mySingleton *obj=nil;
@implementation mySingleton
-(id) init {
if(obj != nil){
[self release];
return obj;
} else if(self = [super init]) {
obj = self;
}
return obj;
}
+(mySingleton*) getSharedInstance {
@synchronized(self){
if(obj == nil) {
obj = [[mySingleton alloc] init];
}
}
return obj;
}
- (id)retain {
return self;
}
- (id)copy {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
if(obj != self){
[super release];
}
//do nothing
}
- (id)autorelease {
return self;
}
-(void) dealloc {
[super dealloc];
}
@end
我已将单例合并到一个类中,因此其他类可以继承单例属性。
Singleton.h :
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
Singleton.m的:
#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
关于 Singleton 类的唯一限制是它是 NSObject 子类。但大多数时候我在代码中使用单例,它们实际上是 NSObject 子类,所以这个类真的让我的生活变得轻松,使代码更简洁。
评论
@synchronized
因为它非常慢,应避免使用。
只是想把这个留在这里,这样我就不会失去它。这个的优点是它可以在InterfaceBuilder中使用,这是一个巨大的优势。这取自我问的另一个问题:
static Server *instance;
+ (Server *)instance { return instance; }
+ (id)hiddenAlloc
{
return [super alloc];
}
+ (id)alloc
{
return [[self instance] retain];
}
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
instance = [[Server hiddenAlloc] init];
}
}
- (id) init
{
if (instance)
return self;
self = [super init];
if (self != nil) {
// whatever
}
return self;
}
公认的答案虽然是编译的,但却是不正确的。
+ (MySingleton*)sharedInstance
{
@synchronized(self) <-------- self does not exist at class scope
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
根据 Apple 文档:
...可以采用类似的方法来同步关联类的类方法,使用 Class 对象而不是 self。
即使使用self works,也不应该,这对我来说看起来像是一个复制和粘贴错误。 类工厂方法的正确实现是:
+ (MySingleton*)getInstance
{
@synchronized([MySingleton class])
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
评论
self
存在,但将其用作传递给的标识符将同步对实例方法的访问。正如 @user490696 所指出的,在某些情况下(如单例),使用类对象是可取的。摘自 Obj-C 编程指南:@synchronized
You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
编辑:此实现已随 ARC 而过时。请查看如何实现与 ARC 兼容的 Objective-C 单例?以正确实现。
我在其他答案中读到的所有初始化实现都有一个共同的错误。
+ (void) initialize {
_instance = [[MySingletonClass alloc] init] // <----- Wrong!
}
+ (void) initialize {
if (self == [MySingletonClass class]){ // <----- Correct!
_instance = [[MySingletonClass alloc] init]
}
}
Apple 文档建议您检查 initialize 块中的类类型。因为子类默认调用 initialize。存在一种不明显的情况,即可以通过 KVO 间接创建子类。如果您在另一个类中添加以下行:
[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Objective-C 将隐式创建 MySingletonClass 的子类,从而导致第二次触发 。+initialize
您可能认为应该隐式检查init块中的重复初始化,如下所示:
- (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;
}
TL;DR,这是我的实现
@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。
评论
我知道有很多关于这个“问题”的评论,但我没有看到很多人建议使用宏来定义单例。这是一种很常见的模式,宏大大简化了单例。
以下是我根据我见过的几个 Objc 实现编写的宏。
Singeton.h
/**
@abstract Helps define the interface of a singleton.
@param TYPE The type of this singleton.
@param NAME The name of the singleton accessor. Must match the name used in the implementation.
@discussion
Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
*/
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;
/**
@abstract Helps define the implementation of a singleton.
@param TYPE The type of this singleton.
@param NAME The name of the singleton accessor. Must match the name used in the interface.
@discussion
Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
*/
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
static BOOL initialized = NO; \
if(!initialized) \
{ \
initialized = YES; \
__ ## NAME = [[TYPE alloc] init]; \
} \
} \
\
\
+ (TYPE *)NAME \
{ \
return __ ## NAME; \
}
使用示例:
MyManager.h
@interface MyManager
SingletonInterface(MyManager, sharedManager);
// ...
@end
MyManager.m
@implementation MyManager
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
SingletonImplementation(MyManager, sharedManager);
// ...
@end
为什么界面宏几乎是空的?头文件和代码文件之间的代码一致性;可维护性,以防您想添加更多自动方法或对其进行更改。
我正在使用 initialize 方法来创建单例,就像这里最流行的答案中使用的一样(在撰写本文时)。
怎么样
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
if (gInstance == NULL) {
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
}
return(gInstance);
}
所以你避免了初始化后的同步成本?
评论
简短的回答:太棒了。
长答案:像......
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
请务必阅读 dispatch/once.h 标头以了解发生了什么。在这种情况下,标题注释比文档或手册页更适用。
使用 Objective C 类方法,我们可以避免以通常的方式使用单例模式,如下所示:
[[Librarian sharedInstance] openLibrary]
自:
[Librarian openLibrary]
通过将类包装在另一个只有类方法的类中,这样就不会意外创建重复的实例,因为我们没有创建任何实例!
我在这里写了一篇更详细的博客:)
评论
扩展 @robbie-hanson 的例子......
static MySingleton* sharedSingleton = nil;
+ (void)initialize {
static BOOL initialized = NO;
if (!initialized) {
initialized = YES;
sharedSingleton = [[self alloc] init];
}
}
- (id)init {
self = [super init];
if (self) {
// Member initialization here.
}
return self;
}
KLSingleton 是:
- 子类(到第n级)
- ARC兼容
- 安全,和
alloc
init
- 延迟加载
- 线程安全
- 无锁(使用 +initialize,而不是 @synchronize)
- 无宏
- 无旋转
- 简单
评论
NSLog(@"initialize: %@", NSStringFromClass([self class]));
+initialize
我的方法很简单,如下所示:
static id instanceOfXXX = nil;
+ (id) sharedXXX
{
static volatile BOOL initialized = NO;
if (!initialized)
{
@synchronized([XXX class])
{
if (!initialized)
{
instanceOfXXX = [[XXX alloc] init];
initialized = YES;
}
}
}
return instanceOfXXX;
}
如果单例已经初始化,则不会输入 LOCK 块。第二个检查 if(!initialized) 是确保当当前线程获取 LOCK 时它尚未初始化。
评论
initialized
volatile
我还没有通读所有的解决方案,所以如果这段代码是多余的,请原谅。
在我看来,这是最线程安全的实现。
+(SingletonObject *) sharedManager
{
static SingletonObject * sharedResourcesObj = nil;
@synchronized(self)
{
if (!sharedResourcesObj)
{
sharedResourcesObj = [[SingletonObject alloc] init];
}
}
return sharedResourcesObj;
}
评论