Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is dispatch_once overkill inside of +[NSObject initialize]?

If I create a singleton inside of +[NSObject initialize], do I need to put my code inside a dispatch_once block like so?

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

EDIT

I'm concerned about this because I want to make sure that all threads will see that I've set Bar after +[Foo initialize] is called. The documentation says +[NSObject initialize] is thread-safe, but does that imply it is memory-safe?

like image 859
Heath Borders Avatar asked Sep 16 '13 19:09

Heath Borders


3 Answers

The answer to your direct question is that you don't need the dispatch_once, but you do need the class check that you have in there, because +initialize will be called once per "non-implementing subclass" as well. It will only be called once for the specific class you care about (Foo), so the dispatch_once is extraneous. Re: thread safety, the +initialize method will complete before any other methods are dispatched to the class (or its instances).

However, you don't describe the desired access pattern, so depending on what you want, you may wish to do the opposite-- if you expect subclasses to have access to Bar, too, then this would be fragile; if the subclass gets initialized before Foo itself is, then the class check will prevent Bar from being created. If you intend this behavior, then use the dispatch_once but remove the class check-- that will generally allow Bar to be created the first time Foo or any of its subclasses are initialized. (Caveat: unless a subclass also overrides +initialize, of course.)

like image 198
Ben Zotto Avatar answered Oct 09 '22 18:10

Ben Zotto


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
like image 45
Heath Borders Avatar answered Oct 09 '22 18:10

Heath Borders


The docs mention the initialize method is only called once per class in a thread-safe manner. Therefore dispatch_once is not necessary.

The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.

EDIT

As @Vincent Gable mentions in the comments, the initialize method may be called more than once if a subclass of Foo does not implement initialize method itself. However, such calls won't be a problem because there is a self == [Foo class] check.

like image 38
murat Avatar answered Oct 09 '22 18:10

murat