创建线程安全单例的正确方法是什么?

What is the proper way to create a thread-safe singleton?

我们有一个 subclassed NSNotificationQueue,它有一些自定义方法,可以在 dispatch_async 调用中排队通知,以便在主线程上退出。

我们有一个 class 方法,sharedQueue,returns 使用 dispatch_once 和静态引用的平均 Objective-C 样式单例。

我的问题是,如果我们从后台线程调用 sharedQueue 方法,那么单例是否会绑定到后台线程,如果该线程消失,单例是否也会被删除?如果是这样,我们是否应该确保在主线程上创建单例?

如果我们需要确保在主线程上创建单例,这是我们的方法:

+ (instancetype)sharedQueue
{
    static dispatch_once_t onceToken;
    static BCOVNotificationQueue *notificationQueue;
    dispatch_once(&onceToken, ^{
        dispatch_sync(dispatch_get_main_queue(), ^{
            notificationQueue = [[BCOVNotificationQueue alloc] initWithNotificationCenter:NSNotificationCenter.defaultCenter];
        });
    });
    return notificationQueue;
}

What is the proper way to create a thread-safe singleton?

技巧很简单:

+ (instancetype)sharedQueue {
    static dispatch_once_t onceToken;
    static BCONotificationQueue *sharedInstance;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[BCONotificationQueue alloc] init];
    });
    return sharedInstance;
}

这是标准的、线程安全的单例实例化。

但是你说:

We have a subclassed NSNotificationQueue ...

这解释了您将其分派到主队列的直觉(因为您正在处理 NSNotificationQueue 并且这是您调用它的线程所特有的)。但是你不希望你的单例同步调度到主队列。我建议您将单例本身的实例化(使用上述模式)与其所需的 NSNotificationQueue 分离。

让我们暂时假设,无论您在哪里调用 BCONotificationQueue,您的意图都是 post 到主线程。与其子类化 NSNotificationQueue,不如将其设为不透明 NSObject,其私有实现包装底层 NSNotificationQueue,如下所示:

//  BCONotificationQueue.h

@import Foundation;

NS_ASSUME_NONNULL_BEGIN

@interface BCONotificationQueue: NSObject

@property (class, readonly) BCONotificationQueue *sharedQueue NS_SWIFT_NAME(shared);

- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;

@end

NS_ASSUME_NONNULL_END

//  BCONotificationQueue.m

#import "BCONotificationQueue.h"

@interface BCONotificationQueue ()
@property (nonatomic, strong) NSNotificationQueue *queue;
@end

@implementation BCONotificationQueue

+ (BCONotificationQueue *)sharedQueue {
    static dispatch_once_t onceToken;
    static BCONotificationQueue *sharedInstance;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[BCONotificationQueue alloc] init];
    });
    return sharedInstance;
}

- (instancetype)init {
    if ((self = [super init])) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.queue = [[NSNotificationQueue alloc] initWithNotificationCenter:NSNotificationCenter.defaultCenter];
        });
    }

    return self;
}

- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.queue enqueueNotification:notification postingStyle:postingStyle];
    });
}

@end

因此,我们将像对 Objective-C 所做的那样实例化我们的单例,但在幕后我们将异步调度包装的 NSNotificationQueue 的实例化(避免任何死锁风险)回到主队列。包装的 enqueueNotification 也会做同样的事情,确保所有通知队列操作都发生在主(串行)队列上,同时仍然享受 BCONotificationQueue 包装器的单例行为。