jblaker
jblaker

Reputation: 147

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

We have a subclassed NSNotificationQueue that has some custom methods that enqueue a notification inside of a dispatch_async call in order to get out on the main thread.

We have a class method, sharedQueue, that returns your average Objective-C style singleton using dispatch_once and a static reference.

My question is if we call the sharedQueue method from a background thread is that singleton then tied to the background thread, and should that thread go away will the singleton also be removed? If so, should we ensure we create the singleton on the main thread?

This is our approach for if we need to ensure the singleton is created on the main thread:

+ (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;
}

Upvotes: 1

Views: 387

Answers (1)

Rob
Rob

Reputation: 437592

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

The technique is simply:

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

This is the standard, thread-safe instantiation of singletons.

But you say:

We have a subclassed NSNotificationQueue ...

That explains your intuition about dispatching this to the main queue (because you’re dealing the NSNotificationQueue and that’s particular to which thread you invoked it). But you don’t want your singleton to be dispatching synchronously to the main queue. I’d suggest you decouple the instantiation of the singleton itself (using the above pattern) from the NSNotificationQueue that it needs.

Let’s assume, for a second, that your intention was to post to the main thread regardless of where you invoked BCONotificationQueue. Instead of subclassing NSNotificationQueue, you instead just make it an opaque NSObject, whose private implementation wraps the underlying NSNotificationQueue like so:

//  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

and

//  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

So, we’re going to instantiate our singleton like we always do with Objective-C, but behind the scenes we’re going to dispatch the instantiation of the wrapped NSNotificationQueue asynchronously (avoiding any deadlock risks) back to the main queue. And the wrapped enqueueNotification will do the same, ensuring that all of the notification queue operations happen on the main (serial) queue, while still enjoying singleton behavior of the BCONotificationQueue wrapper.

Upvotes: 1

Related Questions