Singletons In Swift Explained
A singleton is a class of which exactly one instance exists, that can be globally accessed. How do you create a singleton in Swift? And why should or shouldn’t you?
In this tutorial, we’ll dive into singletons in Swift. You learn what the singleton design pattern is, and why it’s useful. We’ll discuss the syntax for creating singletons in Swift. And we’ll get into good and bad use cases for singletons.
Ready? Let’s go.
What Is A Singleton?
A singleton is a class of which only one instance exists. A few examples:
- A company has only one
CEO
- An API class has only one serial request queue
- An operating system has only one file system
- A solar system body revolves around one gravitational point
- An app that does I/O has only one default
FileManager
- An airplane has only one flight deck
The second attribute of a singleton is that it has a global point of access. You can access a singleton, eg. call functions on it, from anywhere in your app’s code.
So, to summarize:
- A singleton is a class that has only one instance
- It can be accessed globally, i.e. anywhere in your code
In practical iOS development, you use singletons often. Typical classes like NotificationCenter
, UserDefaults
, SKPaymentQueue
and FileManager
have shared
or default
properties that are singletons.
At other times, you may wish to create a singleton yourself. A good use case is an API
class that exposes singleton instance via its shared
property. You use API.shared.makeAPICall()
to access the API through a single, unified instance. This allows you, for instance, to manage API calls serially.
Before we discuss when singletons are best used (and when not), let’s find out how to code a singleton in Swift.
Coding A Singleton In Swift
This is the best way to create a singleton in Swift:
class API
{
static let shared = API()
private init()
{
// Set up API instance
}
}
And here’s how you use the singleton:
API.shared.doSomething()
We’re creating an API
class that has one static property called shared
. This property cannot be changed once set, because it’s a constant, and it’s declared statically.
That means we can access the shared
property via the class API
. This is often called a class property. Compare this to a normal instance property, which can only be accessed via an instance of a class.
What’s interesting is that the shared
property initializes an instance of API
within the API
class. We’re sort of creating an API
object that can be accessed through the API
class. But there’s more…
The class initializer init()
is marked with private
. This private
keyword ensures that the API
class can only be initialized within the API
class.
In other words, you cannot create an instance of API
outside of the API
class! This ensures that the API
object we’ve created is the only instance in our code. After all, you can’t create more of it.
And now we’ve ensured the API
class conforms to the two attributes of a singleton:
- Thanks to the static property
shared
, theAPI
instance can be accessed globally - Thanks to the
private init()
, theAPI
class cannot be initialized outside of theAPI
class
This all may sound a little abstract to you, so let’s expand the previous example with some more practical code. Here’s what:
class API
{
static let shared = API()
var isRequestPending = false
private init() { }
func makeAPIRequest()
{
if isRequestPending {
return
}
isRequestPending = true
// Make the API HTTPS request...
}
func onReturnAPIRequest()
{
isRequestPending = false
// Do something with request data...
}
}
The API
class is mostly the same. It’s still a singleton, and it still uses those static let shared = API()
and private init()
bits of code.
Here’s what’s changed:
- The
API
class now has anisRequestPending
property. This is where the danger begins… See how theisRequestPending
boolean ensures that only one API request can be done at a time? (Note thatisRequestPending
is an instance property.) - The
API
class also has amakeAPIRequest()
function. Imagine that we can use this function to get some data back from a webservice API, like Twitter’s. In the function, you can see that a request can only be made when no other request is currently pending. - The
API
class also has anonReturnAPIRequest()
function. This function is invoked when the API request returns, i.e. online data has been downloaded into the app. TheisRequestPending
boolean is set tofalse
again, and the request data is processed.
And here’s how we can use the API
singleton anywhere in our code:
API.shared.makeAPIRequest()
There’s something else we need to discuss. The API
class now manages something called state. You can see “state” as a feeling: you’re either happy or you’re sad, or you’re angry, and so on. You can switch from one state to the other.
The API
class can switch between two states:
- A state in which
isRequestPending
isfalse
- A state in which
isRequestPending
istrue
As you’ll learn in the next section, state and singletons can wreak all sorts of havoc on your code. Managing state poorly is the single biggest reason for singleton misuse.
When To Use Singletons
When do you use singletons? The book Design Patterns: Elements Of Reusable Object-Oriented Software by the Gang of Four has the following to say. 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
That’s complex, but what it boils down to is:
- Use a singleton when your code requires no more than one instance of a class (i.e., the CEO in the company)
- And when it must be accessible from anywhere in your code (i.e., the file system)
Another use case is subclassing. A global variable in your code can’t be easily subclassed, so that’s why you use a singleton class. Additionally, singletons can be unit tested by using dependency injection. You replace the API
instance by an APIMock
instance, and gain the ability to unit test API calls without making the actual network requests.
And when do you not use singletons? To answer that question, we’ll have to go back to the state principle we discussed earlier.
A common pitfall for beginner iOS developers is to manage state and its dependencies poorly. Imagine you’re building an app that uses the API
class we worked with earlier.
Every time you expand the API class, you tack on more and more properties, such as:
- A
userID
property that keeps track of the logged in user, once thelogin()
API call has been made - A
tweets
property with Twitter data, once thegetTweets()
call has been made - A
spinner
property with aUIActivityIndicatorView
that you add to a view controller when a request has started
At first, this makes a lot of sense to do. After all, the API
class can be accessed anywhere in your code. So, in the Tweet View Controller you can use the API.shared.tweets
array, and in the Settings Controller you can use userID
to quickly tell the API whose settings to change.
Unfortunately, your state is now all over the place. The API
class has dependencies to a bunch of classes that aren’t related to the single responsibility of the API class. Your code has become a bowl of spaghetti, all tangled up. The code may work OK, but it’s impossible to maintain and extend.
Let’s look at an example. The onReturnAPIRequest()
function we defined earlier is on the brink of becoming tightly coupled…
Here’s what we’re considering:
- The
onReturnAPIRequest()
is called when an API webservice request returns, i.e. when data comes into the app. This data needs to go somewhere – a Tweet View Controller for example. How do you pass the data from theAPI
to the view controller? - An obvious choice is to just create a reference to the
viewController
in theAPI
class. When the data comes in, you can code something likeviewController.tweets = tweetsData
. This is poor architecture, unfortunately, because now theAPI
and the view controller are tightly coupled. It’s impossible (or hard) to unit test, and likely to create problems when extending either class. - It’s better to choose a mechanism that doesn’t tightly couple both classes. One option would be to pass a closure to
onReturnAPIRequest()
, which is executed when the request returns. This closure can then contain code to handle the incoming data. Another option would be to useNotificationCenter
to pass the data to the view controller, or to use aDatabase
class to handle the data.
The singleton design pattern has gained some controversy, simply because it’s easy to misuse. When using singletons, be mindful of state and dependencies. Just because it’s easy to have global access to state, it doesn’t mean it’s a good idea.
Further Reading
And that’s all there is to it! Getting to know singletons is a worthwhile objective, especially if you’re interested in app architecture and systems design.
Want to learn more? Check out these resources: