5

の中にシングルトンを作成する場合、そのようにコードをブロックの+[NSObject initialize]中に入れる必要がありますか?dispatch_once

static NSObject * Bar;
@implementation Foo
+ (void)initialize {
  if (self == [Foo class]) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      Bar = [NSObject new];
    });
  }
}
@end

編集

が呼び出されBarた後に設定したことをすべてのスレッドが確認できるようにしたいので、これについて心配しています。+[Foo initialize]ドキュメント+[NSObject initialize]にはスレッドセーフであると書かれていますが、それはメモリセーフであることを意味しますか?

4

3 に答える 3

4

Bill Bumgarner says that dispatch_once is Apple's recommended practice now.

Relating to thread and memory-safety of +initialize, thanks to this tweet, I found the relevant runtime sources to check. objc-initialize.mm says:

 * Only one thread is allowed to actually initialize a class and send 
 * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.

Classes may be initialized on different threads, and objc-initialize.mm has a strategy to avoid them deadlocking:

*  +initialize deadlock case when a class is marked initializing while 
 *  its superclass is initialized. Solved by completely initializing 
 *  superclasses before beginning to initialize a class.
 *
 *  OmniWeb class hierarchy:
 *                 OBObject 
 *                     |    ` OBPostLoader
 *                 OFObject
 *                 /     \
 *      OWAddressEntry  OWController
 *                        | 
 *                      OWConsoleController
 *
 *  Thread 1 (evil testing thread):
 *    initialize OWAddressEntry
 *    super init OFObject
 *    super init OBObject            
 *    [OBObject initialize] runs OBPostLoader, which inits lots of classes...
 *    initialize OWConsoleController
 *    super init OWController - wait for Thread 2 to finish OWController init
 *
 *  Thread 2 (normal OmniWeb thread):
 *    initialize OWController
 *    super init OFObject - wait for Thread 1 to finish OFObject init
 *
 *  deadlock!
 *
 *  Solution: fully initialize super classes before beginning to initialize 
 *  a subclass. Then the initializing+initialized part of the class hierarchy
 *  will be a contiguous subtree starting at the root, so other threads 
 *  can't jump into the middle between two initializing classes, and we won't 
 *  get stuck while a superclass waits for its subclass which waits for the 
 *  superclass.

Additionally, class initialization state variables, are guarded by a monitor_t, which is actually defined as:

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} monitor_t;

Since it is a p_thread_mutex, and p_thread calls implement memory barriers, it is equally safe to use:

static NSObject * Bar;
@implementation Foo
+ (void)initialize {
  if (self == [Foo class]) {
    Bar = [NSObject new];
  }
}
@end

and

static NSObject * Bar;
@implementation Foo
+ (void)initialize {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Bar = [NSObject new];
  });
}
@end
于 2014-03-17T14:32:53.360 に答える
1

ドキュメントinitializeには、メソッドがスレッドセーフな方法でクラスごとに 1 回だけ呼び出されることが記載されています。したがってdispatch_once、必要ありません。

ランタイムは、クラス、またはそれを継承するクラスがプログラム内から最初のメッセージを送信される直前に、プログラム内の各クラスに一度だけ初期化を送信します。(したがって、クラスが使用されていない場合、メソッドが呼び出されることはありません。) ランタイムは、初期化メッセージをスレッドセーフな方法でクラスに送信します。スーパークラスは、サブクラスの前にこのメッセージを受け取ります。

編集

@Vincent Gable がコメントで言及しているように、サブクラスがメソッド自体を実装していないinitialize場合、メソッドは複数回呼び出される可能性があります。ただし、チェックがあるため、このような呼び出しは問題になりません。Fooinitializeself == [Foo class]

于 2013-09-16T19:49:02.647 に答える