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?
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.)
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With