What’s the difference between a singleton and a shared instance in Swift?
Published on: April 19, 2021A common pattern on iOS, and in Swift, is to define an instance of an object that you can access from any place in your app. Common examples are URLSession.shared
, FileManager.default
, and UserDefaults.standard
.
These objects can all be considered shared instances, or globally available instances. Defining a shared instance is commonly done as follows:
struct DataProvider {
static let shared = DataProvider()
// useful properties and methods
}
It's common for developers to call this a singleton, or a singleton instance.
The singleton pattern is a pattern that allows developers to specify an object that can only ever have one instance within the context of an application. This brings us to the reason that this DataProvider
is not an actual singleton object.
There's nothing stopping you from writing the following code:
let providerOne = DataProvider()
let providerTwo = DataProvider()
Since we can create more instances of DataProvider
, this means that DataProvider.shared
is not a singleton. It's simply a convenient, shared, instance of DataProvider
that's associated with the DataProvider
type. This means that we can easily access this shared instance through DataProvider.shared
.
If we wanted to write a more correct singleton object, we could make the initializer for DataProvider
private. This would mean that we can no longer create new instances of DataProvider
:
struct DataProvider {
static let shared = DataProvider()
// useful properties and methods
private init() {
// now we can only create new instances within DataProvider
}
}
Of course, there's still a possibility that you add an extra static property to DataProvider
and that you create more than one instance that way.
Personally, I would consider the above implementation good enough though, and I think you understand the point.
One important note about this example of a singleton (and the shared instance too for that matter) is that even though a static
property is evaluated lazily, it's thread-safe. This means that we're guaranteed that the assigningment to the static
property is atomic, and that we don't end up with multiple instances even if we access the property from multiple threads at the same time.
To learn more about how this works, check out this post from Jesse Squires. If you're not sure what atomic means in programming, read this post on my website.
Also, big thanks to Josh Asbury for telling me more about how static let
is assigned atomically, and pointing me to Jesse's site.
In summary, a singleton is an object that you can only have one instance of. A shared instance is a convenience instance but it's not necessarily the only instance of an object.