Today, I’m studying singletons in the iOS Lead Essentials course.
Conceptually, singletons are simple. They are a pattern for making sure that an object only has one instance, and provides a single point of access to that instance. The canonical definition is based on the mathematically definition of a singleton:
A singleton in mathematics, also known as a unit set or one-point set, is a set with exactly one element. For example, the set {0} is a singleton whose single element is 0.
– Wikipedia: Singleton (mathematics)
The Singleton pattern in software design refines this definition in some important ways. A singleton object:
- Ensures that only one instance can be created
- Provides easy access to that instance
- Controls its instantiation
In practice, this implies some specific behavior from our object:
- It is responsible for keeping track of its sole instance
- Ensures that no other instance can be created, by intercepting requests for creating new instances
- Provides a way to access the sole instance
While Singletons have a reputation for being an anti-pattern, there are times when a Singleton pattern can be quite helpful. From the book Design Patterns: Elements of Reusable Object-Oriented Software:
Use the Singleton pattern when
- there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point.
- when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code.
There are some important benefits from using a Singleton:
- Controlled access: since the Singleton class encapsulates its sole instance, it can strictly control how and when clients access it.
- Reduced name space: since the Singleton class encapsulates its instance, it avoids polluting the namespace with global variables, making it an improvement over global variables.
- Allows for a variable number of instances: Since all access to the Singleton is via a well-known access point, changing the number of underlying instances later is easy, as only the operation that grants access to the Singleton needs to change.
- More flexible than class operations: While class operations (e.g.
class funcfunctions in Swift) can be used to package a singleton’s functionality, that technique can make it more difficult to change a design to allow for more instances.
Singletons with an uppercase S
Alright, that’s a lot of theory. Let’s use this definition to start building a Singleton. Let’s start simple:
import UIKit
final class ApiClient {
static let instance = ApiClient()
private init() {}
}
let client = ApiClient.instance
This is a Singleton. I’ve used an upper-case S for here, because this class adheres strictly to the canonical definition. As we’ll see later, there are other patterns that replicate this functionality, but deviate in interesting and useful ways.
This class checks all the boxes:
- The
init()method is markedprivate, so it cannot be called explicitly. - The only way to access this class’s instance is by calling its
static let instanceproperty. - The
instanceproperty is immutable, so it cannot be set toniland then reinitialized - Marking the class
finalprevents us from subclassing the class. This is important because a subclass could be instantiated separately.
There’s a problem with this, however. The canonical definition also requires the object to be extensible. Our final declaration makes it impossible to subclass. But in Swift, we can use Extensions.
import UIKit
final class ApiClient {
static let instance = ApiClient()
private init() {}
}
extension ApiClient {
func foo() {}
}
let client = ApiClient.instance
client.foo()
There are limits to this approach to extending a Singleton. Specifically, we cannot override functionality with an Extension. We can only add behavior. So if you need to be able to override functionality, you will need to make your Singleton subclass-able, and remove the final declaration.
It’s a trade off:
- If you want people to be able to change existing behavior of your singleton, then you’ll need to make it subclass-able, and deal with the consequences.
- If not, make it
final, which will limit the extensibility to only being able to add behavior.
Singletons with a lowercase S
Sometimes this strict Singleton pattern is too restrictive. A good example of this is the URLSession class:
URLSession documentation
URLSessionhas a singletonsharedsession (which doesn’t have a configuration object) for basic requests. It’s not as customizable as sessions you create, but it serves as a good starting point if you have very limited requirements. You access this session by calling the shared class method. For other kinds of sessions, you create aURLSessionwith one of three kinds of configurations…
URLSession provide access to a shared singleton object as a convenience. It implements all of the requirements of a Singleton (uppercase), with one exception: it allows you to instantiate your own objects for your own needs.
In the strictest sense, then, the URLSession class is not, itself, a singleton. But as part of its implementation, it provides a convenience shared singleton. Notably, this shared singleton does adhere to the definition of Singleton:

- ✅ Ensures that only one instance (of
shared) can be created - ✅ Provides easy access to that instance
- ✅ Controls its instantiation
Testing Singletons
Let’s add a ViewController to our example above, and see what happens when we try to make it testable.
import UIKit
struct LoggedInUser {}
final class ApiClient {
static let instance = ApiClient()
private init() {}
func login(completion: (LoggedInUser) -> Void) {}
}
let client = ApiClient.instance
class LoginViewController: UIViewController {
func didTapLoginButton() {
ApiClient.instance.login() { user in
// show next screen
}
}
}
Now let’s say we want to test the didTapLoginButton() with unit tests, and without actually making an API request. We would need to replace the ApiClient with a mocked version. But two aspects of our class conspire to make this impossible:
finalmeans we cannot create aMockApiClientsubclass- the
static let instanceproperty cannot be reassigned
This Singleton cannot be tested. Not in Swift, anyway. But there are other ways to do this.
Subclassing + Mock
One approach would be to make our ApiClient subclass-able. Then we can inject our ApiClient instance into our ViewController. That way, we can replace our ApiClient with a MockApiClient subclass during tests.
import UIKit
struct LoggedInUser {}
class ApiClient {
static let instance = ApiClient()
private init() {}
func login(completion: (LoggedInUser) -> Void) {}
}
let client = ApiClient.instance
class MockApiClient: ApiClient {}
class LoginViewController: UIViewController {
var api = ApiClient.instance
func didTapLoginButton() {
api.login() { user in
// show next screen
}
}
}
Global State Masquerading as a Singleton
There’s a subtle change we can make to this that seems to make it even easier to test. Let’s make the ApiClient.instance a var instead of a let:
import UIKit
struct LoggedInUser {}
class ApiClient {
static var instance = ApiClient()
private init() {}
func login(completion: (LoggedInUser) -> Void) {}
}
let client = ApiClient.instance
class MockApiClient: ApiClient {
init() {}
}
class LoginViewController: UIViewController {
func didTapLoginButton() {
ApiClient.instance.login() { user in
// show next screen
}
}
}
To make this work in unit tests, we’d need to make sure to:
setUp(): make sure to replace the instance with theMockApiClientinstance
tearDown(): make sure to replace the original instance of theApiClient
This seems to be cleaner, too. The LoginViewController no longer needs to have the ApiClient injected.
The challenge here is that the semantics seem to indicate that this is a Singleton pattern. The property named “instance” and the “private init()” are hallmarks of the Singleton pattern.
But this is no longer a Singleton (or even a singleton). This is mutable global state.
Fixing the Global State Problem
Let’s change this code so that we contain the global state within our instance, and go back to this being a singleton (lowercase s):
import UIKit
struct LoggedInUser {}
class ApiClient {
static let shared = ApiClient()
func login(completion: (LoggedInUser) -> Void) {}
}
class MockApiClient: ApiClient {}
class LoginViewController: UIViewController {
var api = ApiClient.shared
func didTapLoginButton() {
api.login() { user in
// show next screen
}
}
}
What did we do?
- First we revert the
static varto astatic let. This gets rid of our mutable global state. - Then we removed out
private init(). This let’s people instantiate their own instance, which will be useful for mocking. - Then we re-add our
var apiproperty in the ViewController. This let’s us inject an ApiClient, which will need when mocking during unit tests. - Lastly, we rename
instance–>shared, to reflect the fact that we still provide a singleton instance.
Singletons and Dependencies
Let’s start growing our app. We’ll add a FeedViewController.
import UIKit
struct LoggedInUser {}
struct FeedItem {}
class ApiClient {
static let shared = ApiClient()
func login(completion: (LoggedInUser) -> Void) {}
func loadFeed(completion: ([FeedItem]) -> Void) {}
}
class MockApiClient: ApiClient {}
class LoginViewController: UIViewController {
var api = ApiClient.shared
func didTapLoginButton() {
api.login() { user in
// show feed screen
}
}
}
class FeedViewController: UIViewController {
var api = ApiClient.shared
override func viewDidLoad() {
super.viewDidLoad()
api.loadFeed { loadedItems in
//update UI
}
}
}
The FeedViewController will need to use the api to load the feed, which it will do in viewDidLoad(). That means our ApiClient will need a loadFeed() function of some kind.
But doing it this way presents a problem. Let’s look at a diagram:

Imagine if each of these were in its own module. The concrete type ApiClient needs to have functions for each of them. But Login doesn’t need loadFeed() or loadFollowers(), and the same is true for the others.
Plus, each time we make a change to ApiClient, everything that depends on it will need to be recompiled. We have source code dependencies between the Login modules and the ApiClient module. We cannot reuse any of these modules without bringing all of these modules with it.
All that the LoginViewController really needs to do is call a function. But right now, to do that, it needs to go through three “layers”:
- Needs to know where to find the ApiClient instance
- Needs to know the concrete type
- Needs to invoke a function to call
Is this a problem?
Maybe. It will depend on our needs. There are tradeoffs. The global state of the ApiClient.shared instance is convenient, but the implicit dependencies it creates can cause problems down the road.
If we need to attain some level of reusability, then these tradeoffs will start to bite us. We’ll want to understand those tradeoffs, and what to do about it.
Extensions to the Rescue
We can use Extensions to solve this problem. Let’s take a look:

Now the ApiClient provides generic functionality, while each module adds its own functionality. Let’s look at the code:
import UIKit
// Api Module
class ApiClient {
static let shared = ApiClient()
func execute(_ : URLRequest, completion: (Data) -> Void) {}
}
class MockApiClient: ApiClient {}
// Login Module
struct LoggedInUser {}
class LoginViewController: UIViewController {
var api = ApiClient.shared
func didTapLoginButton() {
api.login() { user in
// show feed screen
}
}
}
extension ApiClient {
func login(completion: (LoggedInUser) -> Void) {}
}
// Feed module
struct FeedItem {}
class FeedViewController: UIViewController {
var api = ApiClient.shared
override func viewDidLoad() {
super.viewDidLoad()
api.loadFeed { loadedItems in
//update UI
}
}
}
extension ApiClient {
func loadFeed(completion: ([FeedItem]) -> Void) {}
}
In this configuration, the modules are still dependent on ApiClient. If we add some new module, and that module requires changes to the functionality of ApiClient, it could break other modules.
But even so, this is much more flexible. And it may suit our needs just fine, for quite some time.
Inverting the Dependencies
Eventually, we may require more flexibility or reusability. when that time comes, Dependency Inversion is the next step.
Instead of having the modules depend on the ApiClient, what if we have the ApiClient depend on the modules?

This inversion makes a lot of sense. The ApiClient is an implementation details. Most of the time, the business doesn’t care about implementation details. They care about features. That’s why it can be helpful to have logic contained in the modules, and have the implementation details depend on the modules.
Let’s look at an inverted diagram more closely:

Here we are making each of these classes dependent on some kind of interface for the modules – it could be a protocol or a closure, for example. Remember that each of these modules really just needs a function that it can call (closure), or a type that implements a function (protocol).
For the most modular approach, where every module is reusable in another application, we might even consider introducing “adapters”:

This opens up lots of possibilities. Reusability translates into testability.
Do I Need This Much Complexity?
You might not. It is entirely reasonable to progress through refactorings, like the ones above, as an application becomes more complex.
But this isn’t as complex as it looks. For one, it may not be easy, but it is simple:
- Composing is simple
- Changing is simple
- Testing is simple
It’s also worth remembering that diagrams don’t convert directly into code. Just because this diagram looks complex doesn’t mean the code will be.
Let’s start refactoring this to see what it might look like:
import UIKit
// Main module
extension ApiClient {
func login(completion: (LoggedInUser) -> Void) {}
}
extension ApiClient {
func loadFeed(completion: ([FeedItem]) -> Void) {}
}
// Api Module
class ApiClient {
static let shared = ApiClient()
func execute(_ : URLRequest, completion: (Data) -> Void) {}
}
class MockApiClient: ApiClient {}
// Login Module
struct LoggedInUser {}
class LoginViewController: UIViewController {
var login: (((LoggedInUser) -> Void) -> Void)?
func didTapLoginButton() {
login? { user in
// show feed screen
}
}
}
// Feed module
struct FeedItem {}
class FeedService: UIViewController {
var loadFeed: ((([FeedItem]) -> Void) -> Void)?
func load() {
loadFeed? { loadedItems in
//update UI
}
}
}
We’ve done a few things here:
- We added a
Mainmodule - We added properties to each class that contains a closure
- We moved all of the Extensions to the
Mainmodule
There’s still more to do. We’d have to solve the problem of the various struct types in the modules. But as you can see, this refactoring isn’t complicated.
Conclusion
Singletons are tricky to use, and easy to abuse. But they can be powerful, flexible, and testable when set up properly. The structure of a Singleton can evolve to meet the demands and complexity of the app or SDK that uses it. Using techniques like the ones above, we can react to problems as our application evolves.
Surprisingly, some design patterns that seem complex might have implementations that are quite simple, and provide powerful flexibility, both in usage and in testing.

One response to “Singletons, singletons, singletons”
This one lesson has guided me in more PR reviews than I expected.