2012-02-27 7 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) –

答えて

5

はい、実装に大きな欠陥があります。 @synchronized指示はobjc_sync_enterへの呼び出しになり、後でobjc_sync_exitへの呼び出しに変わります。最初にinstanceメソッドを呼び出すと、_instancenilになります。 objc_sync_enter関数は、looking at its source codeのように、nilを渡すとロックされません。

_instanceが初期化されている前に、2つのスレッドが同時にinstanceを呼ぶのであれば、あなたはMySingletonの2つのインスタンスを作成します。

さらに、_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を使用する必要があります。 (ジョシュが指摘したように)

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

注意: 'pthread_once'は' @synchronized' +ダブルチェックロックよりも良い選択です。 – justin

1

What should my Objective-C singleton look like?は、シングルトンのための良い議論ですが、あなたの質問に答えるために:あなたはnilで同期することはできませんので、あなたはクラスオブジェクトで同期をとる、ノー

を動作しないでしょう。どうして?

@synchronizedには、同期するために割り当てられた定数オブジェクトが必要です。それを基準点と考えてください。 _instanceを使用していますが、これは最初はnil(not good)になります。

@synchronized(自己)今

限りの欠陥、あなたはクラス自体で一定のオブジェクトに対して同期たら(:Objective-Cのの素敵な部分は、クラスはあなたが行うことができますので、それ自体がオブジェクトであるということです自己を使用すると)、欠陥のないシングルトンが得られますが、シングルトンにアクセスするたびに同期オーバーヘッドのコストがかかるため、パフォーマンスに大きな影響があります。

これを緩和する簡単なメカニズムは、作成が一度だけ必要であることを識別し、その後すべての呼び出しが同じ参照を返すことです。これはプロセスの生涯にわたって変化しません。

static MySingleton *_instance = nil; 

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

は今、あなたはシングルトンあなたのための二重NULLチェックの実装を持っている:シングルトンを作成した後、我々はそのパフォーマンスヒットを避けるために、nilのチェックであなたの@synchronizationブロックをラップすることができ、これを識別

。ちょっと待って!考慮すべきことがさらにあります!何?何が残ることができますか?このサンプルコードは何もありませんが、シングルトンを作成する際に実行する作業がある場合は、いくつかの考慮事項を加える必要があります。

2番目のnilチェックを見てみましょう追加作業が必要な共通シングルトンシナリオ用の追加コード。

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

このすべてがとてもよく似合う、しかし_INSTANCEリファレンスへのalloc-initedクラスの割り当てと、我々は準備作業を行う地点間の競合状態が存在します。 2番目のスレッドが_instanceが存在することを確認し、準備作業が未定義の(そして潜在的にクラッシュする)結果の競合条件の作成を完了する前に使用することは可能です。

私たちは何をする必要がありますか?さて、シングルトンを_instance参照に割り当てる前に完全に準備しておく必要があります。

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

簡単ですね。私たちはオブジェクトを完全に準備した後まで、シングルトンの_instance参照への割り当てを遅らせることで問題を解決しました。完璧な世界では、そうですが、完璧な世界では生きていないし、完璧なコード...コンパイラを使って外部の力を説明する必要があります。

コンパイラは非常に高度であり、見た目の冗長性を排除することによってコードの効率を向上させるために、冗長性は、_instance参照を直接使用することによって一見避けることができる一時的なポインタの使用を好む。効率の良いコンパイラを呪います!

この問題が発生しているすべてのプラットフォームで提供される低レベルのAPIがあります。私たちは、tmp変数の準備と、iOSとMac OS Xプラットフォーム用のOSMemoryBarrier()と呼ばれる_instance参照の割り当てとの間にメモリバリアを使用します。これは単に、バリアの前のコードを後続のコードとは独立して考慮する必要があるため、コンパイラのコード冗長性の解釈を排除するというコンパイラのインジケータとして機能します。ここで

がnilチェックで私たちの新しいコードです:ジョージによって

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

、私たちはそれを持っていると思います! 100%スレッドセーフなdouble-NULLチェックのシングルトンアクセサです。それは過労ですか?パフォーマンスとスレッドの安全性が重要かどうかによって異なります。ここでは、最終的なシングルトンの実装です:あなたはObjective-Cで行うには準備作業を持っていない場合

#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; 
} 

は今、単にのalloc-inited MySingletonを割り当てることだけで結構です。しかし、C++では、一時的なメモリバリアブルバリアのトリックを行う必要があります。どうして?オブジェクトへの割り当てと参照への代入は、オブジェクトの構築前に行われるためです。あなたがC++を使用しない場合は

_instance = new MyCPPSingleton(someInitParam); 

は用事、(効果的に)だから、

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

と同じですが、ない場合 - あなたはダブルの適用を予定している場合を念頭にそれを維持することを確認してくださいC++でNULLチェックのシングルトン。

関連する問題