Reputation: 147
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
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