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

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.

Architecting SwiftUI apps with MVC and MVVM

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.

class Singleton {
	static let shared = Singleton()
}

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.

let otherInstance = Singleton()

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:

class Singleton {}

let sharedSingleton = Singleton()

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.

class Singleton {
	static let shared: Singleton = {
		let instance = Singleton()
		// Setup code
		return instance
	}()
}

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.

class Singleton {
	static let shared: Singleton = {
		let instance = Singleton()
		// Setup code
		return instance
	}()
	
	private init() {}
}

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.

*** Assertion failure in -[UIApplication init], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3698.93.8/UIApplication.m:1256
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'There can only be one UIApplication instance.'

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.

struct User: Codable {
	let name: String
	let email: String
	let bio: String
}

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

class StorageController {
	private let userFileURL = FileManager.default
		.urls(for: .documentDirectory, in: .userDomainMask)
		.first!
		.appendingPathComponent("User")
		.appendingPathExtension("plist")
	
	func fetchUser() -> User? {
		guard let data = try? Data(contentsOf: userFileURL) else {
			return nil
		}
		let decoder = PropertyListDecoder()
		return try? decoder.decode(User.self, from: data)
	}
	
	func save(_ user: User) {
		let encoder = PropertyListEncoder()
		if let data = try? encoder.encode(user) {
			try? data.write(to: userFileURL)
		}
	}
}

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.

class StorageController {
	private let userFileURL = FileManager.default
		.urls(for: .documentDirectory, in: .userDomainMask)
		.first!
		.appendingPathComponent("User")
		.appendingPathExtension("plist")
	
	static let shared = StorageController()
	
	private init() {
		guard fetchUser() == nil else { return }
		let user = User(
			name: "Tiffany Reid",
			email: "[email protected]",
			bio: "It was a humorously perilous business for both of us. For, before we proceed further, it must be said that the monkey-rope was fast at both ends; fast to Queequeg's broad canvas belt, and fast to my narrow leather one. So that for better or for worse, we two, for the time, were wedded; and should poor Queequeg sink to rise no more, then both usage and honour demanded, that instead of cutting the cord, it should drag me down in his wake.")
		save(user)
	}
	
	func fetchUser() -> User? {
		guard let data = try? Data(contentsOf: userFileURL) else {
			return nil
		}
		let decoder = PropertyListDecoder()
		return try? decoder.decode(User.self, from: data)
	}
	
	func save(_ user: User) {
		let encoder = PropertyListEncoder()
		if let data = try? encoder.encode(user) {
			try? data.write(to: userFileURL)
		}
	}
}

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.

class ProfileViewController: UIViewController {
	@IBOutlet weak var nameLabel: UILabel!
	@IBOutlet weak var bioLabel: UILabel!
	@IBOutlet weak var emailButton: UIButton!
	
	override func viewDidLoad() {
		super.viewDidLoad()
		guard let user = StorageController.shared.fetchUser() else { return }
		nameLabel.text = user.name
		bioLabel.text = user.bio
		emailButton.setTitle(user.email, for: .normal)
	}
}

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

class EditProfileViewController: UITableViewController {
	@IBOutlet weak var nameTextField: UITextField!
	@IBOutlet weak var emailTextField: UITextField!
	@IBOutlet weak var bioTextView: UITextView!
	
	override func viewWillAppear(_ animated: Bool) {
		super.viewWillAppear(animated)
		guard let user = StorageController.shared.fetchUser() else {
			return
		}
		nameTextField.text = user.name
		emailTextField.text = user.email
		bioTextView.text = user.bio
	}
}

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:

class ProfileViewController: UIViewController {
	@IBOutlet weak var nameLabel: UILabel!
	@IBOutlet weak var bioLabel: UILabel!
	@IBOutlet weak var emailButton: UIButton!
	
	var storageController: StorageController?
	
	override func viewWillAppear(_ animated: Bool) {
		super.viewWillAppear(animated)
		guard let user = storageController?.fetchUser() else {
			return
		}
		nameLabel.text = user.name
		bioLabel.text = user.bio
		emailButton.setTitle(user.email, for: .normal)
	}
}

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

class EditProfileViewController: UITableViewController {
	@IBOutlet weak var nameTextField: UITextField!
	@IBOutlet weak var emailTextField: UITextField!
	@IBOutlet weak var bioTextView: UITextView!
	
	var storageController: StorageController?
	
	override func viewWillAppear(_ animated: Bool) {
		super.viewWillAppear(animated)
		guard let user = storageController?.fetchUser() else {
			return
		}
		nameTextField.text = user.name
		emailTextField.text = user.email
		bioTextView.text = user.bio
	}
}

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.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
	
	var window: UIWindow?
	
	func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
		guard let navigationController = window?.rootViewController as? UINavigationController,
			let profileViewController = navigationController.viewControllers.first as? ProfileViewController else {
				return true
		}
		let storageController = StorageController()
		profileViewController.storageController = storageController
		return true
	}
}

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.

class ProfileViewController: UIViewController {
	@IBOutlet weak var nameLabel: UILabel!
	@IBOutlet weak var bioLabel: UILabel!
	@IBOutlet weak var emailButton: UIButton!
	
	var storageController: StorageController?
	
	override func viewWillAppear(_ animated: Bool) {
		super.viewWillAppear(animated)
		guard let user = storageController?.fetchUser() else {
			return
		}
		nameLabel.text = user.name
		bioLabel.text = user.bio
		emailButton.setTitle(user.email, for: .normal)
	}
	
	override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
		if let navigationController = segue.destination as? UINavigationController,
			let editProfileViewController = navigationController.viewControllers.first as? EditProfileViewController {
			editProfileViewController.storageController = storageController
		}
	}
}

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.

Architecting SwiftUI apps with MVC and MVVM

It's easy to make an app by throwing some code together. But without best practices and robust architecture, you soon end up with unmanageable spaghetti code. In this guide I'll show you how to properly structure SwiftUI apps.

GET THE FREE BOOK NOW

10 thoughts on “Swift Singletons: A Design Pattern to Avoid (With Examples)”

  1. If I want to change state from v3 and it should reflect back in v1 in view will appear . How this depency injection will work

    Reply
  2. The java world embraced dependency injection a good decade ago, it’s not a cure all, it ‘injects’ its own problems. The reason apple uses it for say UserDefaults or FileManager is that it makes perfect sense for such trivial things.

    Where dependency injection makes sense is with regards to generic code.
    Say you want to provide a Storage mechanism that can store things in the file system, over some server using http post over ssl or some mysql server.

    In this case you will end up with 3 concrete classes that implement the Storage protocol.

    Latter on you can ‘inject’ the desired implementation, so you can Store in the cloud and/or locally without any major code change to the UI layer.

    Reply
  3. In my case, I have to get string resource when the app start, and a detail viewcontroller uses that resource, main viewcontroller does not need that. In this case, I need a global, shared intstance. Singleton solve the problem very easily.
    If I use injection, I have to hand over the resource instance through each viewcontroller.

    Reply
  4. Just found this post. It makes some very good points that I will spend some time digesting. I have been doing software development in various forms since 1989. One of the truisms that I have found to be constant is that is never appropriate to answer any question of style/technique with, “never.”

    Reply
  5. Several things….

    First, there is one clear Use Case where singleton’s are appropriate (if not preferred); stateless services.

    Second: Absent customization at initialization, there is absolutely zero reason to embed a singleton as a static member inside a class. Simply declare it as a global object. For example:

    let singleton = Singleton()
    struct Singleton {
    //do something
    }

    Third, as can be shown by the above example, singleton’s do not have to be implemented as classes. Structs work just fine. If the Use Case shouts out for a class, use one. But there’s no reason you have to.

    Finally, a quick digression on dependency injection…

    First, DI requires several elements:
    1. A need to inject specific implementations of a logical singleton at runtime.
    2. Some protocol defining the contract for this logical singleton.
    3, Multiple implementations of the protocol (the thing being injected) – otherwise whyt have DI in the first place
    4. Some mechanism for binding/injecting an implementation at runtime.

    Second, you essentially have to develop – and understand – an entire framework around what is logically a singleton just so you can inject different implementation at runtime. If the Use Case require this type of flexibility, then roll it in. But you’re paying a price for doing so if you don’t need this flexibility. And this price goes up over time as you try and maintain things.

    Finally, runtime DI isn’t often needed – especially for stateless services. The Use Cases in such situation tend to be contrived scenarios around testing. Are there situation where you want runtime DI? Sure. But often compile time bindings work just fine. Just swap implementations and recompile. This is a helluva lot less overhead and very easy to understand 18 months down the road.

    Reply

Leave a Comment