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

时间:2019-03-27 19:46:22

标签: ios objective-c multithreading singleton

我们有一个子类NSNotificationQueue,该子类具有一些自定义方法,这些方法在dispatch_async调用中加入一个通知,以便退出主线程。

我们有一个类方法sharedQueue,该方法使用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;
}

1 个答案:

答案 0 :(得分:1)

  

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

技术很简单:

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

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

但是你说:

  

我们有一个子类NSNotificationQueue ...

这说明了您将其分配到主队列的直觉(因为您正在处理NSNotificationQueue,并且特别是您对哪个线程进行了调用)。但是,您不希望将您的单身人士同步分派到主队列。我建议您将单例本身的实例化(使用上述模式)与所需的NSNotificationQueue分开。

让我们假设一秒钟,您的意图是发布到主线程,无论您在何处调用BCONotificationQueue。您不必将其子类化为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包装器的单例行为。