Thread safe singleton in swift
Thanks to @rmaddy comments which pointed me in the right direction I was able to solve the problem.
In order to make the property foo
of the Singleton
thread safe, it need to be modified as follows:
class Singleton {
static let shared = Singleton()
private init(){}
private let internalQueue = DispatchQueue(label: "com.singletioninternal.queue",
qos: .default,
attributes: .concurrent)
private var _foo: String = "aaa"
var foo: String {
get {
return internalQueue.sync {
_foo
}
}
set (newState) {
internalQueue.async(flags: .barrier) {
self._foo = newState
}
}
}
func setup(string: String) {
foo = string
}
}
Thread safety is accomplished by having a computed property foo
which uses an internalQueue
to access the "real" _foo
property.
Also, in order to have better performance internalQueue
is created as concurrent. And it means that it is needed to add the barrier
flag when writing to the property.
What the barrier
flag does is to ensure that the work item will be executed when all previously scheduled work items on the queue have finished.
What is the proper way to create a thread-safe singleton?
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.
Swift: Thread safe Singleton, why do we use sync for read?
It's all in what you want. By changing get user to async, then you need to use a callback to wait for the value.
func getUser(id: String, completion: @escaping (Result<User>) -> Void) -> Void {
concurrentQueue.async {
do {
let user = try storage.getUser(id)
completion(.value(user))
} catch {
completion(.error(error))
}
}
}
func setUser(_ user: User, completion: @escaping (Result<()>) -> Void) {
concurrentQueue.async(flags: .barrier) {
do {
try storage.setUser(user)
completion(.value(()))
} catch {
completion(.error(error))
}
}
}
That changes the API of get user, so now when calling get user, a callback will need to be used.
Instead of somethings like this
do {
let user = try manager.getUser(id: "test")
updateUI(user: user)
} catch {
handleError(error)
}
you will need something like this
manager.getUser(id: "test") { [weak self] result in
switch result {
case .value(let user): self?.updateUI(user: user)
case .error(let error): self?.handleError(error)
}
}
Assuming you have somethings like a view controller with a property named manager
and methods updateUI()
and handleError()
Thread Safety for a getter and setter in a singleton
You are correct that those getters that you've written are not thread safe. In Swift, the simplest (read safest) way to achieve this at the moment is using Grand Central Dispatch queues as a locking mechanism. The simplest (and easiest to reason about) way to achieve this is with a basic serial queue.
class MySingleton {
static let shared = MySingleton()
// Serial dispatch queue
private let lockQueue = DispatchQueue(label: "MySingleton.lockQueue")
private var _name: String
var name: String {
get {
return lockQueue.sync {
return _name
}
}
set {
lockQueue.sync {
_name = newValue
}
}
}
private init() {
_name = "initial name"
}
}
Using a serial dispatch queue will guarantee first in, first out execution as well as achieving a "lock" on the data. That is, the data cannot be read while it is being changed. In this approach, we use sync
to execute the actual reads and writes of data, which means the caller will always be forced to wait its turn, similar to other locking primitives.
Note: This isn't the most performant approach, but it is simple to read and understand. It is a good general purpose solution to avoid race conditions but isn't meant to provide synchronization for parallel algorithm development.
Sources:
https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html
What is the Swift equivalent to Objective-C's "@synchronized"?
Is this code correct example of thread safe Singleton design pattern?
This is guaranteed to be safe by the JLS. See the holder pattern: "since the class initialization phase is guaranteed by the JLS to be sequential, i.e., non-concurrent, no further synchronization is required in the static getInstance method during loading and initialization."
The holder pattern is more complex than what you want, but the important part is that static final Something INSTANCE = new Something()
is safe no matter which class it is declared in. The benefit of the holder pattern compared to what you have is that the singleton won't be initialized until the first time it is used. This is helpful if you want to access other static members in your Singleton
class when the cost of initializing the Singleton
instance is expensive.
As Lewis_McReu and user6690200 pointed out, you should declare the INSTANCE
field final
in order to ensure that you don't accidentally assign a different Singleton
instance to the variable. You should also declare a private no-argument Singleton()
constructor to prevent other instances from being created. To make it even more bulletproof, you should declare the Singleton
class final
so that you can't subclass it with a public
constructor.
Using a dispatch_once singleton model in Swift
tl;dr: Use the class constant approach if you are using Swift 1.2 or above and the nested struct approach if you need to support earlier versions.
From my experience with Swift there are three approaches to implement the Singleton pattern that support lazy initialization and thread safety.
Class constant
class Singleton {
static let sharedInstance = Singleton()
}
This approach supports lazy initialization because Swift lazily initializes class constants (and variables), and is thread safe by the definition of let
. This is now officially recommended way to instantiate a singleton.
Class constants were introduced in Swift 1.2. If you need to support an earlier version of Swift, use the nested struct approach below or a global constant.
Nested struct
class Singleton {
class var sharedInstance: Singleton {
struct Static {
static let instance: Singleton = Singleton()
}
return Static.instance
}
}
Here we are using the static constant of a nested struct as a class constant. This is a workaround for the lack of static class constants in Swift 1.1 and earlier, and still works as a workaround for the lack of static constants and variables in functions.
dispatch_once
The traditional Objective-C approach ported to Swift. I'm fairly certain there's no advantage over the nested struct approach but I'm putting it here anyway as I find the differences in syntax interesting.
class Singleton {
class var sharedInstance: Singleton {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: Singleton? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = Singleton()
}
return Static.instance!
}
}
See this GitHub project for unit tests.
Related Topics
@Noescape Attribute in Swift 1.2
How to Create Swift Class for Category
How to Make Class Methods/Properties in Swift
Why Use Required Initializers in Swift Classes
Find Difference in Seconds Between Nsdates as Integer Using Swift
What's the Difference Between Struct Based and Class Based Singletons
Swift 2/iOS 9 - Libz.Dylib Not Found
Any Way to Iterate a Tuple in Swift
Swift Protocol Error: 'Weak' Cannot Be Applied to Non-Class Type
Rounding in Swift with Round()
How to Convert a Uiimage to a Cvpixelbuffer
Safariviewcontroller: How to Grab Oauth Token from Url
How to Require That a Protocol Can Only Be Adopted by a Specific Class
Why Do Enums Have Computed Properties But Not Stored Properties in Swift