2012-02-27 45 views
1

最直接,最簡單的實現想這iOS的這個單例實現的任何潛在缺陷?

static MySingleton *_instance = nil; 

    + (MySingleton *) instance 
{ 
    @synchronized (_instance) 
    { 
     if (_instance == nil) 
     { 
      _instance = [[MySingleton alloc] init]; 
     } 

     return _instance; 
    } 
} 

其實我知道像 Implementing a Singleton in iOS 單等,並在彈出template

所以在這裏,我的問題是「上面的任何缺陷的幾個熱門職位實施「?

+0

你還應該看看[你的ObjC單例是什麼樣的?](http://stackoverflow.com/questions/145154/what-does-your-objective-c-singleton-look-like) – 2012-02-27 02:41:40

回答

5

是的,您的實施有一個很大的缺陷。 @synchronized指令變成objc_sync_enter的呼叫以及稍後致電objc_sync_exit。當您第一次致電instance方法時,_instancenil。如果您通過nil,則objc_sync_enter函數不會鎖定,如looking at its source code所示。

因此,如果兩個線程在_instance初始化之前同時調用instance,則將創建MySingleton的兩個實例。

此外,你應該把_instance變量放在函數中,除非你有理由將它暴露給整個源文件。

的首選實施的iOS 4.0單身訪問,後來使用了非常有效的dispatch_once功能,看起來像這樣:

+ (MySingleton *)sharedInstance { 
    static MySingleton *theInstance; 
    static dispatch_once_t once; 
    dispatch_once(&once, ^{ 
     theInstance = [[self alloc] init]; 
    }); 
    return theInstance; 
} 

dispatch_once功能不可用的iOS 4.0之前,所以如果你真的需要支持較舊的iOS版本(不太可能),則必須使用效率較低的@synchronized。既然你不能在nil同步,你的類對象上進行同步:

+ (MySingleton *)sharedInstance { 
    static volatile MySingleton *theInstance; 
    if (!theInstance) { 
     @synchronized (self) { 
      if (!theInstance) 
       theInstance = [[self alloc] init]; 
     } 
    } 
    return theInstance; 
} 
+0

注意:'pthread_once'比'@ synchronized' +雙重檢查鎖定更好。 – justin 2012-02-27 04:13:21

1

What should my Objective-C singleton look like?是單身了很好的討論(如喬希指出的),而是要回答你的問題:

不,不管用。爲什麼?

@synchronized需要一個分配的常量對象來進行同步。把它看作是一個參考點。你正在使用_instance,它最初將是零(不好)。 Objective-C中的一個很好的部分是課程本身的對象,所以你可以這樣做:

@synchronized(個體經營)

現在儘可能的缺陷,一旦你同步對常量對象,它是類本身(使用自己)你將擁有一個無缺陷的單身人士,但是每次訪問可能產生重大性能影響的單身人士時,你都會承擔同步開銷的代價。

緩解這一問題的簡單機制是確定創建只需要進行一次,之後所有後續調用都將返回相同的引用,這些引用永遠不會改變整個流程生命週期。

確定這一點,我們可以換你@synchronization塊在零檢查,以避免性能下降單身創建後:

static MySingleton *_instance = nil; 

+ (MySingleton *) instance 
{ 
    if (!_instance) 
    { 
     @synchronized (self) 
     { 
      if (!_instance) 
      { 
       _instance = [[MySingleton alloc] init]; 
      } 
     } 
    } 
    return _instance; 
} 

現在你有一個雙無效檢查實施你單身。可是等等!還有更多需要考慮的事情!什麼?剩下什麼?對於這個示例代碼,沒有任何內容,但是在創建單例時需要執行的工作,我們需要做一些考慮......

讓我們看看第二個零檢查並添加一些額外的代碼用於需要額外工作的常見單例場景。

if (!_instance) 
{ 
    _instance = [[MySingleton alloc] init]; 
    [_instance performAdditionalPrepWork]; 
} 

這一切看起來很棒,但存在的頁頭,inited類的分配到_instance參考和我們進行準備工作點之間的競爭條件。如果在準備工作完成創建具有未定義(並可能崩潰)結果的爭用條件之前,第二個線程可以看到_instance存在並使用。

那麼我們需要做什麼?那麼,我們需要在分配給_instance引用之前完全準備單例。

if (!_instance) 
{ 
    MySingleton* tmp = [[MySingleton alloc] init]; 
    [tmp performAdditionalPrepWork]; 
    _instance = tmp; 
} 

很簡單吧?我們已經通過延遲將單例分配給_instance引用來解決問題,直到完全準備好對象之後,對吧?在一個完美的世界裏,是的,但我們並不是生活在一個完美的世界中,事實證明,我們需要用我們完美的代碼來解釋外部的力量......編譯器。

編譯器非常先進,努力通過消除看起來的冗餘來提高代碼的效率。冗餘如使用臨時指針,似乎可以通過直接使用_instance引用來避免。詛咒高效的編譯器!

雖然沒關係,但每個平臺都有一個低級API提供這個問題。我們將在tmp變量的準備和賦值_instance引用之間使用內存屏障,也就是iOS和Mac OS X平臺的OSMemoryBarrier()。它只是作爲編譯器的一個指示器,應該獨立於後面的代碼考慮代碼之前的代碼,從而消除編譯器對代碼冗餘的解釋。

這是我們的新代碼在零檢查:

if (!_instance) 
{ 
    MySingleton* tmp = [[MySingleton alloc] init]; 
    [tmp performAdditionalPrepWork]; 
    OSMemoryBarrier(); 
    _instance = tmp; 
} 

通過喬治,我認爲我們已經得到了它!一個100%線程安全的雙NULL檢查單例訪問器。這是否過分矯枉過正?取決於性能和線程安全性對您是否重要。下面是最終實現單:

#include <libker/OSAtomic.h> 

static MySingleton *_instance = nil; 

+ (MySingleton *) instance 
{ 
    if (!_instance) 
    { 
     @synchronized (self) 
     { 
      if (!_instance) 
      { 
       MySingleton* tmp = [[MySingleton alloc] init]; 
       [tmp performAdditionalPrepWork]; 
       OSMemoryBarrier(); 
       _instance = tmp; 
      } 
     } 
    } 
    return _instance; 
} 

現在,如果你沒有準備工作在Objective-C做的,只是分配的alloc-inited MySingleton就好了。但是,在C++中,操作順序決定了必須執行帶有內存限制的臨時變量技巧。爲什麼?因爲對象的分配和賦值將在構造對象之前發生。

_instance = new MyCPPSingleton(someInitParam); 

是(有效)一樣

_instance = (MyCCPSingleton*)malloc(sizeof(MyCPPSingleton)); // allocating the memory 
_instance->MyCPPSingleton(someInitParam);      // calling the constructor 

所以,如果你從來沒有使用C++,請不要介意,但如果你做的 - 一定要記住這一點,如果你計劃在應用雙在C++中使用NULL檢查單例。