Thinking up Singletons


While exploring a rescue project, I came across singletons… tons and tons of singletons. There was even a Singletons.swift that just instantiated the lot of them at runtime. Because reasons…

But setting aside whether you should be using the Singleton pattern (the Internet is filled with opinions on that), I hadn’t actually implemented a Singleton in Swift myself.

So after asking around and tinkering a bit, the following Singleton designs presented themselves;

The ‘true’ Singleton

The easiest pattern just uses a single static variable, which doesn’t change.

class Singleton {
    static let sharedInstance = Singleton()
    
    func helloWorld() {
        print("Hello world")
    }
}

Singleton.sharedInstance.helloWorld() // prints 'Hello world'

The mutable Singleton… … …

Sometimes you decided that something has to be a singleton and you don’t want to pass it around objects. However, you still want to test your objects, so you would like to change the Singleton used in that instance. For this, we introduce the setDefaultInstance(:) method.

class ChangeableSingleton {
    private(set) static var sharedInstance = ChangeableSingleton()
    static func setDefaultInstance(instance: ChangeableSingleton) {
        sharedInstance = instance
    }

    var message: String
    
    required init(message: String = "Can I be shorter?") {
        self.message = message
    }
    
    func helloWorld() {
        print(message)
    }
}

ChangeableSingleton.sharedInstance.helloWorld() // prints "Can I be shorter?"
ChangeableSingleton.setDefaultInstance(ChangeableSingleton(message: "Shorter is better!"))
ChangeableSingleton.sharedInstance.helloWorld() // prints "Shorter is better!"

The Singleton protocol with associated types (PAT)

If you need a lot of Singletons, you might want to make them all conform to the same protocol. Let’s create a Singletonable that allows you to easily create new Singleton objects.

protocol Singletonable {
    associatedtype SingletonType
    static var sharedInstance: SingletonType { get }
}

class PATSingleton: Singletonable {
    private(set) static var sharedInstance = PATSingleton()
}

You can even use this create additional compositions;

protocol Printable {
    var message: String { get }
    func helloWorld()
}

extension Printable {
    func helloWorld() {
        print(message)
    }
}

class PATSingletonWithParameters: Singletonable, Printable {
    private(set) static var sharedInstance = PATSingletonWithParameters()
    private(set) var message: String
    
    init(message: String = "Message") {
        self.message = message
    }
}

PATSingletonWithParameters.sharedInstance.helloWorld()

I would have liked to create a protocol for the ChangeableSingletonable that inherits from Singletonable with a default implementation for setDefaultInstance(:) as well, but I can’t figure out how to do that.

You can find the above code on Github as well;