Swift Singletons: A Design Pattern to Avoid (With Examples)

Swift Singletons: A Design Pattern to Avoid

I often recommend avoiding singletons in iOS apps, especially to pass data between view controllers.

I thought that most developers knew why the singleton pattern should be avoided and what is the correct approach.

So I was surprised when I discovered that many iOS developers don’t know this.

In this article, I’ll show you what a singleton is, why you should not use singletons in your apps and what you should use instead.

The 5 most common misconceptions about SwiftUI

GET THE FREE BOOK NOW

Contents

What is a singleton in Swift?

First of all, let’s clarify the definition of a Swift singleton.

Part of the confusion about singletons comes from the unclear distinction between global variables, singletons, and shared instances.

On Wikipedia, the singleton pattern is defined as:

A software design pattern that restricts the instantiation of a class to one.

Only classes can be singletons in Swift. Structures are value types, and copies are created every time you pass a value to a function or a type. This clearly defeats the purpose of singletons.

But the definition of singleton that Apples provides in its documentation is different:

You use singletons to provide a globally accessible, shared instance of a class.

This definition is not unique to Apple’s documentation. The Wikipedia article I linked above also states that “An implementation of the singleton pattern must provide global access to that instance.”

But Apple’s definition does not mention restricting the number of instances to one.  And indeed, that is what usually happens in iOS development.

How to implement a singleton class in Swift

If you are here just to grab some code, I’ll show you how Swift singletons work first and spare you more searched. Later in this article, I will explain why singletons are a shortcut you should not use.

In Swift 4, creating a singleton according to Apple’s definition can be as short as three lines of code.

The shared property is static, so you can access it from anywhere in your code. This ensures global access.

Notice that you can still create other instances of the Singleton class in your code if you want. We haven’t restricted those.

Technically, the shared property of Singleton is a global variable. There is no distinction between a singleton and a variable defined in the main scope of a Swift file.

The following code is equivalent:

The sharedSingleton constant is also directly accessible from anywhere in your code.

In practice though, the first option is the preferred one, since it puts the shared singleton instance in the namespace of its class. But keep in mind that, since global variables and singletons are the same, they have the same problems.

If you need to perform some extra setup when you create a shared singleton instance, you can do so using an initialization closure.

This technique is not limited to singletons. You can use it to initialize any property in a type.

This technique works because parentheses immediately follow the closure. This causes the closure to be executed, and its result to be assigned to the shared static property.

Finally, if you want to follow the original singleton pattern definition and also ensure that there is always only one instance of the Singleton class, you can make its initializer private.

Why Swift singletons are so popular in iOS development

There is no denying that singletons have been popular for a long time in iOS development. To some extent, they still are. There are many reasons for that.

First of all, Apple does not show many Swift design patterns in its documentation, but singletons are right there in the list.

So it looks like Apple encourages the use of singletons. Moreover, Apple’s documentation shows how to create a singleton, but does not discuss the implications. That makes it look like the singleton pattern has no drawbacks.

The second reason is that Apple uses singletons extensively in the iOS SDK. Here are a few examples of the most common ones:

  • UIApplication.shared
  • UserDefaults.standard
  • FileManager.default
  • URLSession.shared
  • OperationQueue.main

The UIApplication singleton makes sense since it represents the currently running app. There should be only one instance of this class.

In fact, even Apple follows the original definition of singleton in this case. You cannot create another UIApplication instance in your app.

Unfortunately, the UIApplication class comes from Objective-C, where there is no concept of private and public methods. So, the UIApplication initializer is public, and the compiler allows you to use it anywhere in your code.

But at runtime, it will throw an exception and crash your app.

The other singletons in the list above though are not “real” singletons. You can create instances of UserDefaults, FileManager, URLSession, and OperationQueue. In fact, it is often better to work with separate instances of these classes.

These singletons exist only for the convenience of a globally accessible instance. And that’s the biggest reason why singletons are popular. They are very convenient.

Singletons allow you to conveniently share state between view controllers

In any app with more than a screen, you will sooner or later need to access a shared resource. To understand why singletons are so popular, let’s look at an example app that uses a singleton.

Let’s make a small social networking app. Such apps usually have a screen where the user can see his profile and a screen where to edit the information. You can grab the code for the full app on GitHub.

mockup of an example app using a swift singleton

Credits: banner photo by James Mckellar, profile photo by Alex Perez

Below, I will concentrate only on the code for shared resources. If you want to see how to build interfaces like the ones above, check my article on Auto Layout.

First of all, let’s build the UI in an Xcode storyboard.

the storyboard for the swift singleton app

The navigation controllers are needed to display the navigation bar at the top of the screen. You can find more details in my article on iOS storyboards.

In our code, we start representing the data of a user in the model, using a Swift struct.

The User structure conforms to Codable so we can easily save it to and read it from the disk.

The code to read and save a User data goes into a model controller

Here I used property lists to store data on the disk, but iOS offers also other storage technologies.

At this point, developers new to iOS face a usual problem. How do you get a shared instance of  StorageController into both view controllers?

The typical answer is: make it a singleton.

I put some default data in the initializer to have some to run the app. That’s not usually necessary in a real app where the user provides the data instead, but I’m keeping things simple for the sake of the example.

The singleton makes populating the interfaces of both view controllers straightforward. Here is the view controller for the Profile screen.

And this is the view controller for the Edit profile screen:

And, indeed, it works!

 

Wasn’t that easy?

That is why singletons are widespread. You get to solve the complex problem of sharing resources between view controllers with a few, quick lines of code.

But it’s also a wrong solution.

Sure, you save some time taking this shortcut, but this code will bite you back later when the architecture of your app grows or when you add unit testing to your project.

The many reasons why singletons are a terrible solution

While singletons are a quick solution, they are a dirty trick that many consider being an anti-pattern. If you search for “singleton anti-pattern” on Google, you will find many results like this one.

So, let’s look at the many reasons why singletons are a weak solution.

  • Singletons provide a globally mutable shared state. Ironically, the definition of singleton is one of the reasons why singletons are bad. The example above shows that we need a way to access any shared resource. But such access should be limited only to the parts in an app’s architecture that need it. The global accessibility of singletons makes shared resources accessible from anywhere, especially from code that should not have any access. Even value types like Swift structures and enumerations can access a singleton, which is a bad practice. When any part of an app can access or change the global state, you get weird and hard to fix bugs.
  • Singletons encourage code with unclear separation. One of the fundamental principles of good software development is separation of concerns. Singletons make access to shared resources a widespread concern across many objects. When a concern is spread too wide, it’s hard to keep track of it when specifications change and code needs to be updated. This leads to inconsistencies in your code base that can lead to severe problems like data loss or corruption.
  • The assumption that there will always be only one instance of a singleton is often broken. This change leads to significant refactoring of all code that relies on a singleton. Take, for example, the StorageController we designed above. In the future, we might need to have different instances of that object to handle different locations in the file system or different storage solutions. Core Data, for example, is usually used with only one managed context, but also supports multiple ones.
  • Singletons are not transparent. Singletons allow a class to access a shared resource without making it clear to external objects. A developer that is not familiar with the internal implementation of a class might mistakenly believe that an instance is an isolated object. In reality, the internal state of such class can change at any time. Or worse, calling a method can cause unexpected side effects.
  • Singletons carry state around for the lifetime of the application. There are cases in which you need to reset the shared state. When you can have multiple instances, you can discard the old one and then create a new one. In a singleton, instead, resetting state might not be so natural and might require specific and complex code.
  • Singleton classes often become monoliths. Since it’s easy to access a singleton from anywhere, the chances are high that code that needs to be shared ends inside an existing singleton. Massive view controllers are not the only monolithic objects you should avoid in iOS. The same happens to singletons.
  • Code that uses singletons is hard or impossible to test. Unit testing treats tested code as a separate, independent unit. The internal state of a unit should change only when the testing code interacts with its interface. Additionally, the same sequence of method calls should always lead to the same internal state. Singletons break these two assumptions. Finally, the testing of complex objects often implies replacing dependencies with test doubles like stubs, spies and mocks. Some languages allow you to access the internal state of an object through a mocking framework. In Swift, this is not possible, so there is no way to replace a singleton with a test double.

The correct way to share resources: replacing singletons with dependency injection

If singletons are the wrong solution, what is then the correct one?

The critical point here is the distinction between singletons and shared resources.

In any real app, shared resources are necessary and unavoidable. There are always parts of an app’s architecture that need to be accessed from many places. Some examples are:

  • The current global state of the app.
  • The disk storage where data is saved, be it the file system, a database, the user defaults of the app, or a Core Data managed object context.
  • A URL session that groups related network requests.
  • A shared operation queue to prioritize, sequence, and schedule the asynchronous tasks of the app.

What makes singletons wrong is not the sharing of a resource. It’s the global accessibility.

So, the solution is not removing shared state, but using a different mechanism to share resources.

That mechanism is dependency injection.

Dependency injection is a mechanism where an object receives its dependencies from another, external object. Contrast it with the singleton pattern, where an object retrieves its dependencies.

Dependency injection is the de-facto technique to share resources between objects without using singletons. More generally, it makes the dependencies of an object replaceable. This is useful in many contexts, including unit testing with test doubles.

Dependency injection is simple to implement. All you need is a public property for each dependency you want to inject in an object.

In our ProfileViewController this is how it looks:

The StorageController instance is now injectable from the outside of the ProfileViewController. We do the same in the EditProfileViewController.

How to inject dependencies into view controllers at the launch of an app and in segues

Now, the question is: where should these dependencies come from?

That depends.

There are many techniques to pass data between view controllers in iOS.

In simple architectures like MVC, the app delegate injects shared resources into the initial view controller of the app.

Here we have to dig into the navigation controller, to get to the ProfileViewController. This is not necessary, but it’s common to have a container as the initial view controller of an app.

Other view controllers get their dependencies when a transition happens, and a segue is triggered, from the view controller that was previously on screen.

For example, in our app, it’s the ProfileViewController that will pass the shared StorageController instance to the EditProfileViewController when the user taps on the Edit button.

If you run the app, it works exactly as before. But there are no singletons anymore.

In more complex architectures, dependency injection is usually operated by coordinators. I talk about those in the article where I cover my version of MVC, the Lotus MVC Pattern

Conclusions

There are many articles online which try to answer the question: “when is it ok to use a singleton?”

My answer is: never.

That might sound a bit strict, but the drawbacks of singletons outweigh the little benefits of taking the shortcut. You can, and should, always solve the problem using dependency injection.

It must be said that it can be difficult to convince other developers in your team to ditch singletons. This is a message I got from one of my students:

I started to apply your techniques and best practices in my everyday job. But my boss told me I have to use a singleton because it’s more understandable and it’s less code. Although he is an experienced programmer, he told me there is no real reason not to use singletons. I told him everything you wrote about it, but in response, I got something like: “I understand you’ve read some smart books, but you have no idea how it works”

If that’s your case, I can sympathize with you. I hope that this article will help you convince others.

I used singletons only at the beginning of my career when I didn’t know better. The singleton I wrote for this article is the first one I write in years of making iOS apps.

Unfortunately, Apple seems to be hell-bent on using Singletons in its tutorials, including the new one they created for SwiftUI. But even in SwiftUI, a correct architecture is crucial. You can find out more in my free guide below.

The 5 most common misconceptions about SwiftUI

SwiftUI is the future of UI development. And yet, many developers share some misconceptions that prevent them from understanding how SwiftUI works and from using it in real apps.

GET THE FREE BOOK NOW