Thread Safe Singleton in Swift

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



Leave a reply



Submit