Yet another article on Singletons

Yet another article on Singletons

In the last article, we started the slow and steady process of refactoring our ExpensesViewController, and we will continue this process with an eye on testing.
One of the most challenging issues in our class is using the singleton pattern for calling the API.
Let’s take a step back and ask ourselves: what is a singleton?

Singleton

Singleton is a creational design pattern described in the famous GoF (Gang of Four) Design Patterns’ book. Its definition is something along the lines of ensuring only one instance of an object is created and provide a global point of access to that instance.

This is how you make a singleton in Swift.

class Singleton {
    static let sharedInstance = Singleton()
}

We can go further and enforce it by making the class final and the initialiser private.

final class Singleton {
    static let sharedInstance = Singleton()

    private init() {}
}

Aren’t Singletons just global variables? Well, not really, and the difference is very subtle. Since you can access the singleton instance using a static type property, you can assign a closure to this static property if you need to perform additional setup beyond the initialisation.

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

While a global variable is more related to how the developers declare instances.

singleton

What? Is the same title as the above section? No, it’s not: you can see there is lowercase “s”.
A singleton (with lowercase “s”) is a variation of the Singleton which the API still provides a unified access point to a class shared across the app. Still, it also allows the developers to create a new instance.

Sounds weird, doesn’t it? But, if you think about it, it’s present in the iOS SDK. Think about URLSession or UserDefaults. You have the shared instances, but you can also create them.

G.M.S.S. – Global Mutable Shared State

Can you spot the difference between the shared variable of URLSession and URLCache?

Yes, URLCache’s shared variable is not read-only. This means that it can be changed by any process or thread in the app at any time. And this is very risky because it increases the chances of the system being in an inconsistent state.

But it is convenient for testing code that uses singletons, like in our case. See Ambient Context.

Back to our code

If you remember our ExpensesViewController, we are fetching our expenses from API calling getExpenses from API class. Let’s have a look at it.

class API {
    static let shared: API = API()

    func getExpenses(from date: Date, completion: @escaping ([Expense], Error?) -> Void) {
        //...
    }

    func saveExpense(merchant: String, date: Date, amount: Int, currency: Currency, completion: @escaping (Expense?, Error?) -> Void) {
        //...
    }

    func deleteExpense(id: UUID, completion: @escaping (Bool, Error?) -> Void) {
        //...
    }

    func updateExpense(id: UUID, merchant: String, date: Date, amount: Int, currency: Currency, completion: @escaping (Expense?, Error?) -> Void) {
        //...
    }
}

As you can see, it is a singleton (with lowercase “s”), but the shared instance is read-only. We can do a couple of things here:

  1. Change the shared variable into a var, subclass the API class, and overrides all the methods. I genuinely discourage this way. You don’t really want to have a global state in your app and tests, do you?
  2. Temporarily create a variable of type API inside the ExpensesViewController and assign the shared instance (a.k.a. property injection).
class ExpensesListViewController: UIViewController ... {
    ...
    var api: API = .shared
    ...
    func fetchExpenses() {
        api.getExpenses(from: ...
    }
    ...
}

Conclusion

We will eventually refactor the API class from a singleton to something else. For now, we are making our ExpensesViewController testable.

I am not saying that singleton is bad, but it is undeniable that we abused and misused this design pattern, especially in the iOS realm. Just ask yourself if your class REALLY needs to be a singleton. Is your class, when having more than one instance, dangerous? Can it go to an inconsistent state?

In my experience, I believe we all abused the singleton design pattern because of a lack of knowledge of dependency injection, which I will talk about in future articles. And also because it’s widely used in the iOS SDK.

And also, we always think that if we need just one of something, then singleton is the answer, as the API in our case.

Let me know what do you think in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *