2

グローバル変数とメソッドを利用するために、健全なコーディング手法として Singleton を実装しました。実装する前に、 Appleのドキュメントジョン・ワーズワースのブログに従いました。最初に、シングルトン スレッド セーフを作成せず、ブログと Apple のドキュメントで言及されている他のすべてのメソッドと共にこのメソッドを実装しました。

+ (SingletonClass *)sharedManager 
{
  static SingletonClass *sharedManager = nil;
  if (sharedManager == nil) {
    sharedManager = [[super allocWithZone:NULL] init];
}
  return sharedManager;
}

その後、シングルトンをスレッドセーフにするために+ (SingletonClass *)sharedManager、このようなクラスに変更を加えたところ、アプリの起動が停止しました。ブレークポイントを設定すると、dispatch_once取得が 2 回呼び出され、コードの実行がさらに停止します。

+(SingletonClass *)sharedManager
{
  static SingletonClass *sharedManager = nil;
  if (sharedManager !=nil)
  {
    return sharedManager;
  }
  static dispatch_once_t pred;       
  dispatch_once(&pred, ^{
    sharedManager = [SingletonClass alloc];
    sharedManager=[sharedManager init];
});

     return sharedManager;
}

このスレッド セーフ コード スニペットを削除して以前のコードに戻すと、問題なく動作し、コードが実行されます。

ここで、質問する前にデッドロックの可能性について言及しているbbum の回答も見ましたが、問題を理解することはできません。説明や解決策は私にとって役に立ちます。ありがとう。

編集1:

誰かが完全なコードを見たい場合に備えて、そのための要点を作成しました。そこに従ってください。ありがとう。

4

2 に答える 2

7

sharedManager2 つのスレッドがほぼ同時にの 2 番目のバージョンを呼び出すとどうなるか考えてみましょう。

スレッド 1 が最初に呼び出します。をチェックしますsharedManager !=nilが、これは false であるため、 に進みdispatch_onceます。dispatch_onceブロック内で実行し[SingletonClass alloc]、結果を に格納しsharedManagerます。

ここで、スレッド 1 が次の行に進む前に、スレッド 2 がやってきて を呼び出しますsharedManager。スレッド 2 は をチェックしますsharedManager !=nilが、これは true になりました。したがって、 が返さsharedManagerれ、呼び出し元は を使用しようとしますsharedManager。しかし、現時点でsharedManagerは、まだ完全に初期化されていません。良くないね。

完全に初期化されたオブジェクトを設定するsharedManagerまで、設定することはできません。また(borrrdenが指摘したように)、とにかく非常に効率的であるため、上部のチェックは必要ありません。sharedManager !=nildispatch_once

+ (SingletonClass *)sharedManager {
    static dispatch_once_t pred;
    static SingletonClass *sharedManager;
    dispatch_once(&pred, ^{
        sharedManager = [[SingletonClass alloc] init];
    });
    return sharedManager;
}

今、私はあなたの要点を見てきましたが、あなたの問題はここにあります:

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

メソッドはブロックで+[SingletonClass sharedManager]呼び出します。をオーバーライドしないので、 を呼び出します。そしてメソッド呼び出し。へのこの 2 回目の呼び出しでは、まだ への最初の呼び出しの中にいるため、プログラムはハングアップします。+[SingletonClass alloc]dispatch_oncealloc+[SingletonClass alloc]+[SingletonClass allocWithZone:NULL]+[SingletonClass allocWithZone:]+[SingletonClass sharedManager]sharedManagerdispatch_oncedispatch_once

最も簡単な修正は、 の実装を削除することですallocWithZone:sharedManagerのインスタンスを取得して先に進むための唯一のサポートされている方法を文書化するだけSingletonClassです。

鈍感でシングルトンを返すようにしたい場合[[SingletonClass alloc] init]は、繰り返しても複雑です。allocまたはをオーバーライドしようとしないでくださいallocWithZone:。これを行う:

static SingletonClass *sharedManager; // outside of any method

+ (SingletonClass *)sharedManager {
    return sharedManager ? sharedManager : [[SingletonClass alloc] init];
}

- (id)init {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        if (self = [super init]) {
            // initialization here...
            sharedManager = self;
        }
    });
    self = sharedManager;
    return self;
}
于 2013-01-07T08:40:52.087 に答える
1

上部のチェックは必要ありませんif。ステートメントを削除してください。アプリケーションのdispatch_once存続期間中にブロックが 1 回だけ実行されることが保証されるため、最初のチェックは冗長です。

詳細: http ://cocoasamurai.blogspot.jp/2011/04/singletons-your-doing-them-wrong.html

于 2013-01-07T08:31:44.887 に答える