dispatch_once过度杀伤了+ ?
如果我在+[NSObject initialize]
里面创build一个单例,我需要把我的代码放在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
编辑
我很担心这一点,因为我想确保所有的线程都会在调用+[Foo initialize]
之后看到我已经设置了Bar
。 文档说+[NSObject initialize]
是线程安全的,但这是否意味着它是内存安全的?
直接问题的答案是你不需要dispatch_once
,但是你需要在那里进行类检查,因为+initialize
也会被调用一次,也就是每个“非实现的子类” 。 它只会被调用一次你关心的特定类( Foo
),所以dispatch_once
是无关紧要的。 回复:线程安全, +initialize
方法将完成之前任何其他方法分发到类(或其实例)。
然而,你并没有描述所需的访问模式,所以根据你想要的,你可能希望做相反的事情 – 如果你希望子类也可以访问Bar
,那么这将是脆弱的; 如果子类在Foo
本身之前被初始化,那么类检查将阻止创buildBar
。 如果你打算这样做,那么使用dispatch_once
但删除类检查 – 通常会允许在第一次Foo
或其任何子类被初始化时创buildBar
。 (注意:除非子类也覆盖+initialize
,当然 。)
Bill Bumgarner说, dispatch_once
现在是苹果推荐的做法 。
关于+initialize
线程和内存安全性,感谢这个tweet ,我find了相关的运行时间源 。 objc-initialize.mm
说:
* Only one thread is allowed to actually initialize a class and send * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.
类可以在不同的线程上初始化, objc-initialize.mm
有一个避免死锁的策略:
* +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.
另外,类初始化状态variables由monitor_t
来保护, 它实际上定义为 :
typedef struct { pthread_mutex_t mutex; pthread_cond_t cond; } monitor_t;
由于它是一个p_thread_mutex
,并且p_thread调用实现内存屏障 ,所以使用同样安全:
static NSObject * Bar; @implementation Foo + (void)initialize { if (self == [Foo class]) { Bar = [NSObject new]; } } @end
和
static NSObject * Bar; @implementation Foo + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Bar = [NSObject new]; }); } @end
文档中提到的initialize
方法只能以线程安全的方式每个类调用一次。 因此dispatch_once
是没有必要的。
运行时发送初始化给程序中的每个类,恰好在类之前,或者从它inheritance的任何类中,从程序中发送它的第一条消息。 (因此,如果不使用该类,该方法可能永远不会被调用。)运行时以线程安全的方式将初始化消息发送给类。 超类在它们的子类之前收到这个消息。
编辑
正如@Vincent Gable在注释中提到的那样,如果Foo
的子类本身没有实现initialize
方法,则可能会多次调用initialize
方法。 然而,这样的调用将不会是一个问题,因为有一个self == [Foo class]
检查。