2010-02-19 29 views
6

在仔細研究單身人士文學之後,下面是我編造的。 我忘了什麼嗎?這是權威參考計算Objective C單例實現?

@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;  
} 
+1

請不要編輯您的問題的方式,使現有的答案無效或不再相關。 – dreamlax 2010-02-19 04:19:02

+0

爲什麼另一個Objective-C單例線程?有關示例,請參閱http://stackoverflow.com/questions/145154。 – zoul 2010-02-19 06:52:24

+0

難道你只是使用加拉格爾的http://projectswithlove.com/projects/SynthesizeSingleton.h.zip從http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html。 ..? – Fattie 2010-12-23 19:39:01

回答

2

避免了同步鎖最 的

如果你想你的軟件是可靠的,避免工作的構造時間「大部分時間」

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

表4.1。雙重檢查鎖

雙重檢查鎖試圖通過在鎖定之前測試鎖定標準來降低獲取鎖的開銷。由於雙重檢查的鎖可能不安全,系統不提供對它們的明確支持,因此不鼓勵使用它們。

+0

-1爲無關和flamebait。 – 2010-02-19 01:58:30

+0

我認爲他在考慮「大部分時間節省CPU和同步」。 但是,使用現代化的CPU和優化器,它很可能會「大多數時間做正確的事情」。 但我承認Windows的評論是不必要的。 – Darron 2010-02-19 02:00:09

+0

但雙重鎖定_is_不可靠,或者我錯了嗎? *編輯:*我是對的。 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html – glebm 2010-02-19 02:13:23

1

一些建議(更適用於Mac可可超過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; 
} 
+0

- 使用alloc將調用我的allocWithZone覆蓋,這將在nil on sharedInstance,所以這將是一個問題 – Jacko 2010-02-19 02:30:59

+0

啊,當然。忽略那個位然後:) – 2010-02-19 02:32:52

0

singleton_remover功能不會做任何事情,因爲你已經覆蓋release什麼也不做。您的allocWithZone:方法在向共享實例發送retain時也會執行類似的無操作(並且完全忽略指定區域中的分配)。也許你應該有一個標誌來切換你的共享實例是否是無敵的(即不可釋放的)。

無論哪種方式,操作系統都會清理所有的內存。該文檔指出,操作系統只需要一次回收所有內存的速度要比您的應用程序逐漸回收速度快。

如果您共享實例是管理資源總是需要在您的應用程序終止時要清理,你應該有它的登記接收,並在那裏進行清理。

[[NSNotificationCenter defaultCenter] addObserver:self 
             selector:@selector(performCleanup:) 
              name:UIApplicationWillTerminateNotification 
              object:nil]; 
+0

謝謝。在singleton_remover方法中釋放資源有什麼問題? – Jacko 2010-02-19 02:37:33

+0

響應'NS/UIApplicationWillTerminateNotification'是在應用程序終止期間正常處理資源管理的文檔化方式。 – dreamlax 2010-02-19 03:40:18

+0

只在主線程的一般情況下注冊'NS/UIApplicationWillTerminateNotification'是安全的 - 一定要考慮到這一點 – rpetrich 2010-02-19 04:28:46

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已經有很長的一段時間了,我對那些與所有已發表的例子不同的基本機制保持警惕。

我的代碼我目前使用的(這反映了我所看到的其他地方,包括this答案):

+ (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 
} 
+0

有趣的。我的實現完全避免了鎖定,所以性能更好,並且沒有死鎖潛力。另外,你應該在[MySingleton類]上進行同步,因爲self還沒有被初始化。 – Jacko 2010-02-19 03:30:10

+0

@Jacko,那是不正確的,'self'會在任何消息發送給它之前被初始化(即用'+ initialize'),無論它們是調用類還是實例方法。 – dreamlax 2010-02-19 03:53:26

+0

在類級別的方法中,「self」等價於[MyClass class] - 也就是說,BOOL isSelf = self == [MySingleton class];將在上面的sharedInstance方法中設置爲YES。另外,由於代碼被寫入,由於調用序列,不存在死鎖的可能性......但是我同意「初始化」方法可能更好。你如何從課堂外到達你的sharedInstance變量?我沒有看到這種方法。 – 2010-02-19 05:09:39

0

你的實現是線程安全的,似乎涵蓋所有的基礎(+初始化發送thread-由運行時安全)

編輯:很多代碼在atexit函數期間將不安全調用。在主線程上註冊更安全。

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; \ 
} 
+0

Apple表示不要使用名稱以下劃線開頭的方法。下劃線前綴保留並用於Apple的私有方法。 – dreamlax 2010-02-19 05:57:39

+0

非常好的一點(根據http://www.devworld.apple.com/iphone/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocLanguageSummary.html#//apple_ref/doc/uid/TP30001163-CH3-TPXREF108 );編輯以反映 – rpetrich 2010-02-19 06:08:04

+0

爲什麼你需要初始化方法中的自動釋放池? – Jacko 2010-02-19 14:41:05