web-dev-qa-db-ja.com

シングルトンのスレッドセーフなインスタンス化

シングルトンがシングルトンのままであることを保証するために使用する同期方法はどれですか?

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

またはミューテックスを使用していますか?

#import <pthread.h>

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;

+(Foo*)sharedInstance
{
   pthread_mutex_lock(&_mutex);
   if (nil == _sharedInstance)
   {
      _sharedInstance = [[Foo alloc] init];
      ...
   }
   pthread_mutex_unlock(&_mutex);
   return _sharedInstance;
}

うーん..これについて何かコメントはありますか?

33
MacTouch

この質問/回答のディスカッションも必ず読んでください。 Objective-Cでデッドロックを回避するためにalloc呼び出しとinit呼び出しを分離する必要があるのはなぜですか?


競合状態の問題を拡張するため。 realの修正は、アプリケーション内で不確定な初期化を行わないようにすることです。 Indeterminateまたはlazyの初期化により、一見無害な変更(構成、「無関係な」コードの変更)により、簡単に変更できる動作が発生します。等...

プログラムのライフスパンの既知の良好な時点でサブシステムを明示的に初期化することをお勧めします。つまりサブシステムをプログラムの早い段階で本当に初期化する必要がある場合は、_[MyClass sharedInstance];_をアプリデリゲートの_applicationDidFinishLaunching:_メソッドにドロップします(または、必要に応じてさらに早く移動します)余分に防御する)。

初期化をそのメソッドから完全に移動することをお勧めします。つまり_[MyClass initializeSharedInstance];_ここで、_+sharedInstance_は、そのメソッドが最初に呼び出されない場合はasserts()します。

私は便利さのファンですが、25年間のObjCプログラミングは、怠惰な初期化が価値以上のメンテナンスとリファクタリングの頭痛の種であることを教えてくれました。


以下に説明する競合状態は存在しますが、このコードは以下に説明する内容を修正しません。共有インスタンス初期化子の同時実行性について心配しなかったとき、それは数十年の間そうでした。繁栄のために間違ったコードを残す。

コリンとハラルドの両方の正解については、非常に微妙な競合状態があり、悲惨な世界につながる可能性があることに注意してください。

つまり、割り当てられているクラスの_-init_がたまたまsharedInstanceメソッドを呼び出した場合、変数が設定される前に呼び出します。どちらの場合も、デッドロックが発生します。

これは、allocとinitを分離する1回です。最良の解決策であるためにColinのコードをクリブする(Mac OS Xを想定):

_+(MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    // partial fix for the "new" concurrency issue
    if (sharedInstance) return sharedInstance;
    // partial because it means that +sharedInstance *may* return an un-initialized instance
    // this is from https://stackoverflow.com/questions/20895214/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/20895427#20895427

    dispatch_once(&pred, ^{
        sharedInstance = [MyClass alloc];
        sharedInstance = [sharedInstance init];
    });

    return sharedInstance;
}
_

noteこれはMac OSXでのみ機能します。特にX10.6以降とiOS4.0以降。ブロックが使用できない古いオペレーティングシステムでは、ロックまたはブロックベースではない何かを実行するさまざまな手段の1つを使用します。


上記のパターンは、テキストで説明されている問題を実際に防ぐものではなく、発生したときにデッドロックを引き起こします。問題は、dispatch_once()が再入可能ではないため、initsharedInstanceを呼び出す場合、ウェッジシティ

56
bbum

これを行うための最速のスレッドセーフな方法は、Grand Central Dispatch(libdispatch)とdispatch_once()を使用することです。

+(MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        sharedInstance = [[MyClass alloc] init];
    });

    return sharedInstance;
}
38
Colin Wheeler

誰かが気にするなら、ここに同じことのためのマクロがあります:

   /*!
    * @function Singleton GCD Macro
    */
    #ifndef SINGLETON_GCD
    #define SINGLETON_GCD(classname)                            \
                                                                \
    + (classname *)shared##classname {                          \
                                                                \
        static dispatch_once_t pred;                            \
        static classname * shared##classname = nil;             \
        dispatch_once( &pred, ^{                                \
            shared##classname = [[self alloc] init];            \
        });                                                     \
        return shared##classname;                               \
    }                                                           
    #endif
12
Arvin

これ CocoaDevページ あなたのニーズに役立つことができます。

2

誰かが気にするなら、ここに同じことのための別のマクロがあります:)

私見、それは 他のバリエーション と比較してより大きな柔軟性を提供します。

#define SHARED_INSTANCE(...) ({\
    static dispatch_once_t pred;\
    static id sharedObject;\
    dispatch_once(&pred, ^{\
        sharedObject = (__VA_ARGS__);\
    });\
    sharedObject;\
})

使用法、1行の初期化:

+ (instancetype) sharedInstanceOneLine {
    return SHARED_INSTANCE( [[self alloc] init] );
}

使用法、複数行の初期化(コードブロックを中括弧で囲んでいることに注意してください):

+ (instancetype) sharedInstanceMultiLine {
    return SHARED_INSTANCE({
        NSLog(@"creating shared instance");
        CGFloat someValue = 84 / 2.0f;
        [[self alloc] initWithSomeValue:someValue]; // no return statement
    });
}

割り当ての右側での使用法:

- (void) someMethod {
    MethodPrivateHelper *helper = SHARED_INSTANCE( [[MethodPrivateHelper alloc] init] );
    // do smth with the helper
}
// someMethod should not call itself to avoid deadlock, see bbum's answer

この変更では、GCC 複合式 拡張機能(Clangでもサポートされています)とC99 可変個引数マクロのサポート の2つの言語機能を利用します。

前処理後、出力は次のようになります(Xcode5でProduct > Perform Action > Preprocess "YourClassName.m"を呼び出すことで自分でテストできます)。

+ (instancetype) sharedInstanceOneLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[self alloc] init] );
        });
        sharedObject; // this object will be returned from the block
    });
}

+ (instancetype) sharedInstanceMultiLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ({
                NSLog(@"creating shared instance");
                CGFloat someValue = 84 / 2.0f;
                [[self alloc] initWithSomeValue:someValue];
            });
        });
        sharedObject;
    });
}

- (void) someMethod {
    MethodPrivateHelper *helper = ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[MethodPrivateHelper alloc] init] );
        });
        sharedObject;
    });
}
1
skozin