When I first started dabbling with Cocoa development, I almost immediately came into contact with the singleton pattern. Many Cocoa frameworks, including UIKit and Foundation, use the singleton pattern.
What Is the Singleton Pattern
The singleton pattern is an easy to understand design pattern. I recommend reading or watching What Is a Singleton and How To Create One In Swift if you'd like to learn more. In a nutshell, the singleton pattern ensures only one instance of a class is instantiated for the lifetime of the routine or the application.
The singleton pattern ensures only one instance of a class is instantiated for the lifetime of the routine or the application.
The Cocoa frameworks often refer to a shared object or a shared instance. Take a look at these examples. URLSession
and UserDefaults
are both defined in the Foundation framework.
// Shared Session Object
let session = URLSession.shared
// Shared Defaults Object
let userDefaults = UserDefaults.standard
With the definition of the singleton pattern in mind, it appears to be a useful design pattern. Unfortunately, many developers misuse the singleton pattern and use it to conveniently access an object from anywhere in the project. Having global access to the singleton object is no more than a side effect of the singleton pattern. It's not what the singleton pattern is about.
Singletons Everywhere
Whenever I talk about singletons, I like to bring up Maslow's hammer analogy.
I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail. — Abraham Maslow
A surprising number of developers struggle with this problem. The singleton pattern appears to be solving a common problem, accessing an object in various places of the project. A singleton, no more than a fancy global, looks like the perfect fit for the problem. And it is the perfect fit if all you want to do is solve that problem. There's more to the story, though.
Have you ever wondered why some experienced developers consider the singleton pattern an anti-pattern? Why is it that such a useful pattern is often avoided by more senior developers?
Sacrificing Transparency for Convenience
By using singletons, you almost always sacrifice transparency for convenience. But how much are you willing to sacrifice for that little bit of convenience? Convenience should not be high on your priority list if you're working on a software project. Let me show you with an example what the problem is.
By using singletons, you almost always sacrifice transparency for convenience.
A common problem in software projects is user management. It means that a user object needs to be created and managed. There are many solutions to this problem. If you're a fan of the singleton pattern, then you might create a singleton or a manager class that manages the currently signed in user. Only a single user can be signed in at a time. Right?
Problem solved? It's true that you can now access the currently signed in user from anywhere in your project. While this may seem like the best thing since sliced bread, it introduces several problems.
Consider the following question. Which object is allowed to modify the user object? That's easy. The manager object managing the user. But isn't the manager object accessible from anywhere in the project? It implies that the user object can be modified from anywhere in your project. If you're still with me, then I hope this is starting to sound less and less like the great idea it initially appeared to be.
How are you going to make sure that the objects that depend on the currently signed in user are notified when the user object is modified? That's easy. Notifications. Another popular option is to use a pattern similar to Java's observer pattern. You could also use key value observing, but do you really want to observe the properties of an object that's managed by a singleton? That sounds like asking for trouble.
Losing Track of Everything
I agree that the singleton pattern seems convenient and it may occasionally be warranted to use it, but the drawbacks far outweigh the benefits. Unfortunately, the drawbacks are very subtle at first and that's what misleads many, many developers.
Unfortunately, the drawbacks are very subtle at first and that's what misleads many, many developers.
The most important drawback of the singleton pattern is sacrificing transparency for convenience. Consider the earlier example. Over time, you lose track of the objects that access the user object and, more importantly, the objects that modify its properties.
The initial advantage of using a singleton, convenience, becomes the most important problem. Ironic. Isn't it? By using singletons in your project, you start to create technical debt. Singletons tend to spread like a virus because it's so easy to access them. It's difficult to keep track of where they're used and getting rid of a singleton can be a refactoring nightmare in large or complex projects.
Once the singleton pattern becomes an accepted practice in a project, it's difficult to move the project forward without using even more singletons. The structure of the project may seem flexible and loosely coupled, but it's anything but that.
Substituting Singletons With Dependency Injection
As I mentioned earlier, singletons have the tendency to spread like a virus. The cure to solve singleton disease is dependency injection. While a key advantage of the singleton pattern is convenience, the most important advantage of dependency injection is transparency. Transparency is good.
If an object requires a valid user object to do its job, then that user object should be injected as a dependency. It's easy to translate this requirement into code. Take a look at this example.
class User {
var firstName = ""
var lastName = ""
}
class NetworkController {
let user: User
init(user: User) {
self.user = user
}
}
This snippet shows any developer working with the NetworkController
class that it depends on a valid User
instance. This is also known as passing an object by reference. It's less convenient than using a singleton, but it pays dividends in the long run. It adds clarity to the codebase, unambiguously showing which objects depend on which objects.
It's an advantage that passing an object by reference is less convenient. Why is that? It forces you to carefully consider your decision. Does the NetworkController
class need access to the user object? Would it suffice to pass the user's username and password instead? Dependency injection can be a very useful tool to define the requirements of a type.
Dependency injection can be a very useful tool to define the requirements of a type.
Moving Away From Singletons
A few years ago, I started working on a client project that was littered with singletons. Some singletons even referenced other singletons, adding to the problem. After several rounds of refactoring, I managed to eliminate most of the singletons from the project, which drastically increased transparency and robustness.
Have you ever worked on a project nobody in your team wanted to touch? Modifying a handful of lines in one class could cause mayhem in a distant, unrelated component of the project. This is not uncommon for projects that make heavy use of singletons.
Another benefit of ridding a project of singletons relates to testing. Writing unit tests without having to worry about singletons is fantastic. It really is. Combine this with dependency injection and you have a great combination to work with.
Are Singletons Bad
My stance on singletons varies from day to day. If I had a rough day battling singletonitis, I dislike them with a passion. The truth is that singletons aren't inherently bad if they're used correctly. The goal of the singleton pattern is to ensure only one instance of a class is alive at any one time. That, however, is not the goal many developers have in mind when using singletons.
Singletons are very much like the good things in life, they're not bad if used in moderation. The next time you're about to create a singleton, consider your motivation for creating one. Is it convenience? Then you shouldn't create the singleton. Period. That's the simple rule I apply.
If you want to learn more about dependency injection, then I recommend reading or watching Nuts and Bolts of Dependency Injection in Swift. It shows you how you can use dependency injection in your own projects.