6

这是我在仔细研究单身文献后炮制出来的。我忘了什么吗?

@implementation MySingleton

static MySingleton *mySharedInstance = nil;

//called by atexit on exit, to ensure all resources are freed properly (not just memory)  
static void singleton_remover()
{
    //free resources here
}

+ (MySingleton*) sharedInstance{
    return mySharedInstance;
}

+ (void)initialize {
    if (self == [MySingleton class]) {
        mySharedInstance = [[super allocWithZone:NULL] init];
    atexit( singleton_remover );    
    }
}

+ (id)allocWithZone:(NSZone *)zone
{
    return [self sharedInstance];   
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;    
}

- (id)retain
{
    return self;    
}

- (NSUInteger)retainCount
{
    return NSUIntegerMax;  //denotes an object that cannot be released  
}

- (void)release
{
    //do nothing    
}

- (id)autorelease
{
    return self;    
}
4

5 回答 5

2

大部分时间避免了同步锁

如果您希望您的软件可靠,请避免“大部分时间”工作的构造

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

表 4.1。双重检查锁

双重检查锁试图通过在获取锁之前测试锁定条件来减少获取锁的开销。因为双重检查锁可能是不安全的,所以系统不为它们提供明确的支持,并且不鼓励使用它们。

于 2010-02-19T01:55:38.453 回答
1

一些建议(Mac Cocoa 比 iPhone 更多,但它可能对搜索 Objective-c 标签的其他人有用):

  • 不要为 -allocWithZone:NULL 烦恼,只需简单的 -alloc 就可以了。
  • 考虑在可用的情况下使用 dispatch_once() 或 pthread_once()
  • atexit 的使用很聪明,但可能与快速应用程序终止不兼容(不确定这是否适用于 iPhone),因为这有效地杀死 -9s 应用程序

另一种有趣的模式:

+ (Foo *)sharedFoo {
    static Foo *sharedInstance = NULL;
    if (!sharedInstance) {
        Foo *temp = [[Foo alloc] init]; //NOTE: This MUST NOT have side effects for it to be threadsafe
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &sharedInstance)) {
            [temp release];
        }
    }
    return sharedInstance;
}
于 2010-02-19T02:15:53.650 回答
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 orig = @selector(sharedInstance);
            SEL new = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, orig);
            Method newMethod = class_getClassMethod(self, new);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

以及围绕初始化的历史讨论:

我现在看到原始代码实际上很像我的(如下),除了检查锁外的实例。

虽然新的 + (void) 初始化方法很有趣,但我不确定我是否更喜欢这个。现在似乎要获得一个单例实例,您现在必须始终调用:

MySingleton instance = [[MySingleton alloc] init];

这不正确吗?这感觉很奇怪,如果对初始化的调用已经为您锁定,效率会更高吗?对于这个用例,双锁方法似乎可以正常工作,同时也避免了锁定(我认为可能会以双重分配为代价,因为多个线程可能会通过 if)。

另一件看起来很奇怪的事情,如果初始化方法真的很受欢迎,为什么我们在其他地方看不到呢?Objective-C 已经存在了很长时间,我对与几乎所有已发布示例不同的基本机制持谨慎态度。

我目前使用的代码(这反映了我在其他地方看到的,包括这个答案):

+ (MySingleton *)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

+ (id)allocWithZone:(NSZone *)zone 
{
    @synchronized(self) 
    {
        if (sharedInstance == nil) 
        {
            sharedInstance = [super allocWithZone:zone];
            return sharedInstance;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}
于 2010-02-19T02:54:25.717 回答
0

singleton_remover函数不会做任何事情,因为您已经被重写为什么都不release做。您的方法在发送到共享实例allocWithZone:时也会执行类似的无操作(并且完全忽略指定区域中的分配)。retain也许您应该有一个标志来切换您的共享实例是否无敌(即不可释放)。

无论哪种方式,操作系统都会清理所有内存。该文档指出,对于操作系统来说,一次回收所有内存比让您的应用程序慢慢地逐步恢复内存要快。

如果您的共享实例正在管理在您的应用程序终止时始终需要清理的资源,您应该让它注册以接收UIApplicationWillTerminateNotification,并在那里执行清理。

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(performCleanup:)
                                             name:UIApplicationWillTerminateNotification
                                           object:nil];
于 2010-02-19T02:17:16.833 回答
0

您的实现是线程安全的,并且似乎涵盖了所有基础(+initialize 由运行时发送线程安全)

编辑:在函数期间调用很多代码是不安全的atexitUIApplicationWillTerminateNotification在主线程上注册更安全。

edit2:我将我使用的模式提炼并提炼成一个宏。-init在第一次调用时+sharedInstance调用,-dealloc并将在应用程序终止时调用。

#define IMPLEMENT_UIAPP_SINGLETON(class_name) \
static class_name *shared ## class_name; \
+ (void)cleanupFromTerminate \
{ \
    class_name *temp = shared ## class_name; \
    shared ## class_name = nil; \
    [temp dealloc]; \
} \
+ (void)registerForCleanup \
{ \
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \
} \
+ (void)initialize { \
    if (self == [class_name class]) { \
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \
        if ([NSThread isMainThread]) \
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \
        else \
            [self performSelectorOnMainThread:@selector(registerForCleanup) withObject:nil waitUntilDone:NO]; \
        shared ## class_name = [[super allocWithZone:NULL] init]; \
        [pool drain]; \
    } \
} \
+ (class_name *)sharedInstance \
{ \
    return shared ## class_name; \
} \
+ (id)allocWithZone:(NSZone *)zone \
{ \
    return shared ## class_name; \
} \
- (id)copyWithZone:(NSZone *)zone \
{ \
    return self; \
} \
- (id)retain \
{ \
    return self; \
} \
- (NSUInteger)retainCount \
{ \
    return NSUIntegerMax; \
} \
- (void)release \
{ \
} \
- (id)autorelease \
{ \
    return self; \
}
于 2010-02-19T04:16:19.107 回答