Swift - Singleton without global access

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Swift - Singleton without global access



I want to create a Swift Singleton without global access. The pattern which I want to create is to assure that always just one instance of a class exists but this class should not be accessible over the usual global MyClass.shared syntax. The reason for this is that I want the class to be fully and correctly testable (which is not really possible with global Singletons). I will then use dependency injection to pass the single instance from viewcontroller to viewcontroller. So the "access" issue is solved without a global static instance.


MyClass.shared



What I could do is to do basically - nothing. Just create a normal class and trust on the discipline of all developers to not instantiate this class again and again but use it only injected as a dependency. But I would rather have some compiler enforced pattern which prohibits this.



So the requirement is:



My first attempt to solve this was something like this:



enter image description here


class WebService
private static var instances = 0

init()
assertSingletonInstance()


private func assertSingletonInstance()
#if DEBUG
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.isUnitTestRunning.rawValue) == false
WebService.instances += 1
assert(WebService.instances == 1, "Do not create multiple instances of this class. Get it thru the shared dependencies in your module.")

#endif




Remark: passing an argument during launch creates a user defaults value which can be checked during runtime. This is how I know that the current run is a unit test.



Generally this pattern works quite good. My only problem with this - I have to duplicate this code over and over again for every possible singleton. Which is not nice. I would prefer a reusable solution.



One solution for this was to create a Protocol Extension:


protocol Singleton
static var instances: Int get set
func assertSingletonInstance()


extension Singleton
// Call this assertion in init() to check for multiple instances of one type.
func assertSingletonInstance()
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.isUnitTestRunning.rawValue) == false
Self.instances += 1
assert(Self.instances == 1, "Do not create multiple instances of this class. Get it thru the shared dependencies in your module.")

#endif




And then use it in this way:


class WebService: Singleton )
static var instances = 0

init()
assertSingletonInstance()




The problem with this approach is that the instances variable is not private. So someone could just set this variable to 0 before instantiating the class and the check would not work anymore.


instances


private



The other attempt was a Singleton base class. In this case a private static var instances can be used.


Singleton


private static var instances


class Singleton
private static var instances = 0

required init()
assertSingletonInstance()


private func assertSingletonInstance()
#if DEBUG
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.isUnitTestRunning.rawValue) == false
Singleton.instances += 1
assert(Singleton.instances == 1, "Do not create multiple instances of this class. Get it thru the shared dependencies in your module.")

#endif




The problem with this approach is - it does not work. Incrementing Singleton.instance adds 1 to the static instances of the Singleton type and not to the class which derives from the Singleton base class.


Singleton.instance


static instances


Singleton


Singleton



Now I am left with either doing nothing and relying on the discipline and understanding of all developers or at least use the protocol extension with internal or public access.


internal


public



A sample implementation can be found here.



Maybe someone has better ideas for a really clean solution to this problem. I appreciate any hints or a discussion about it. Thanks.





What if you put all your dependencies in a separate module, which builds a library/framework, with only a single exposed "Globals" protocol, with a default instance. The globals protocol provides access to all dependencies (db, web apis, w/e), which could all have internal initializers. From there, it would be easy to make a "MockGlobals" object, which is set to that global instance
– Alexander
yesterday


internal


instance




2 Answers
2



You can use an atomic flag (for thread safety) to mark the singleton as being instantiated:


class Singleton

static private var hasInstance = atomic_flag()

init()
// use precondition() instead of assert() if you want the crashes to happen in Release builds too
assert(!atomic_flag_test_and_set(&type(of: self).hasInstance), "Singleton here, don't instantiate me more than once!!!")


deinit
atomic_flag_clear(&type(of: self).hasInstance)




You mark the singleton as allocated in init, and you reset the flag in deinit. This allows you on one hand to have only one instance (if the original instance doesn't get deallocated), and on the other hand to have multiple instances, as long as they don't overlap.


init


deinit



App code: assuming that you'll keep a reference to the singleton, somewhere, that you inject downstream, then deinit should never be called, which leads to only one possible allocation.


deinit



Unit testing code: if the unit tests properly do the cleanup (the tested singleton gets deallocated after every test), then there will be only one living instance at a certain point in time, which won't trigger the assertion failure.





You would need to synchronize that, because you could end up with multiple objects due to the race condition
– Alexander
yesterday





Good point, @Alexander, updated the answer to include thread safety.
– Cristik
yesterday





That's awesome, thanks!
– Darko
10 hours ago



In response to Cristik's answer:



This is a really nice solution! The type(of: self) solves the base class problem. And releasing the thing in deinit is a great idea to allow the whole thing in unit tests. You are right - I keep the references of all the Singletons "upstream" and inject them afterwards. Perfect.


type(of: self)



I have created a template based on this ideas with a serial queue for the possible race condition problem. I assume this is a better solution then the atomic_flag and more "Swiftish".



Playground code:


import Foundation

class Singleton
static private var instances = 0

// Sync the access to instances
private var serialQueue = DispatchQueue(label: "com.yourcompany.app.singletoncheck")

init()
serialQueue.sync
type(of: self).instances += 1
assert(type(of: self).instances == 1, "Do not create multiple instances of this class living at the same time.")



deinit
type(of: self).instances = 0



class Derived: Singleton



var a: Derived? = Derived()
//a = nil // release to prevent the assertion from failing
var b: Derived? = Derived() // assertion fails here, works!



And here is an even more interesting solution which can be used everywhere without any special knowledge and no assertions at all. It uses a failable initializer.



Playground code:


import Foundation

class Singleton
static private var instances = 0

// Sync the access to instances
let serialQueue = DispatchQueue(label: "com.yourcompany.app.singletoncheck")

// This failable initializer assures that at the same time only one instance of this class exists.
init?()
var singleInstance = false
serialQueue.sync
type(of: self).instances += 1
if type(of: self).instances == 1
singleInstance = true


if !singleInstance
return nil



deinit
serialQueue.sync
type(of: self).instances = 0




class Derived: Singleton
var a = 0
func increment()
serialQueue.sync
a += 1
print(a)




var a = Derived()
a?.increment() // call to synchonized version of increment
//a = nil //either a or b is alive
var b = Derived()

print (a) //prints Optional(__lldb_expr_15.Derived)
print (b) //prints nil



This is in my opinion the "real" Singleton, described by the Gang of four. Global access was only an implementation detail at that time.



So in comparison to the usual Singleton pattern it:



So it has all the advantages of a Singleton, but without the usual problems.





Note that you also need to ensure thread safetiness in deinit.
– Cristik
9 hours ago





Actually, on second thought, you might not need it, since you'd only end up in unit tests to execute deinit, and unit tests usually run on main.
– Cristik
9 hours ago


deinit






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard