Are they a design pattern or a design anti-pattern?
Few design patterns have been scrutinized more closely than singletons, and with good reason: although the idea might seem sensible at first, they all too often become an anti-pattern that hinders testing, gets in the way of concurrent code, and makes your code harder to reason about.
However, like so many parts of development, singletons aren’t universally good or bad – they are a gray area, and trying to insist otherwise is only worthwhile if you’re desperate to drive traffic to your blog.
Don’t agree? You need only look at Apple’s own APIs: they might hide behind the nomenclature of “shared instance”, but they exist in many places - UIApplication
, UIAccelerometer
, UIDevice
, WCSession
, and many more all use singletons. Heck, trying to instantiate an instance of UIApplication
literally crashes your code because it doesn’t make sense to do such a thing. Instead, an application is created for you and you get access to that shared instance – all access to its methods and properties must be funneled through a single place, which allows Apple to tightly control what you can do with it.
However, just because Apple uses singletons doesn’t mean we need to. We all accept that global variables are A Bad Idea, so if you think a singleton is the solution to your problem you need to be pretty darn sure that you aren’t just wrapping global variables up in a fancy name.
At the very least, singletons should offer these advantages over global variables:
Even then I’d recommend considering different solutions, because if you aren’t careful it can be deeply annoying trying to write tests when singletons are involved.
However, if you’ve examined all the alternatives and are convinced that a singleton is the right solution – logging is the classic example – then I want to introduce you to a technique I learned a couple of years ago that has really changed the way I implement singletons. I first read about this solution on Sarah Reichelt’s blog and it’s now second nature for me – I really think it’s the cleanest, simplest way of handling singletons in Swift.
You see, even when singletons are useful I still consider them implementation details – something that happens, but I’d rather not expose them in my API. Swift’s protocol extensions gives us a simple way to use singletons throughout our app without exposing implementation details or restricting us in the future.
As an example, consider the following singleton:
class Logger {
static let shared = Logger()
private init() { }
func log(_ message: String) {
print(message)
}
}
Anywhere you need to log something you can use Logger.shared.log()
, or you could even write a static method that removes the shared
part if you wanted.
But if we don’t want to expose the fact that a singleton is being used, a Swiftier solution is to create a Logging
protocol like this:
protocol Logging {
func log(_ message: String)
}
Anything conforming to that protocol must implement a log()
method. Obviously we don’t want to implement that in all our types, so we can write a simple protocol extension that calls our singleton:
extension Logging {
func log(_ message: String) {
Logger.shared.log(message)
}
}
Finally, we can design any types we want, make them to conform to Logging
, and call log()
freely without those types having to be aware a singleton is being used:
struct MainScreen: Logging {
func authenticate() {
log("Authentication was successful!")
}
}
let screen = MainScreen()
screen.authenticate()
If you adopt this approach you can switch away from using singletons relatively easily – all the places you use them don’t change, and you just need to make them a property of your type instead.
Now, keep in mind that declaring a method inside of a protocol creates extension points for that protocol, which you might not want to do. If you want to encourage all types that conform to Logging
to use your existing log()
methods, just leave it out of the protocol like this:
protocol Logging {
}
This solution doesn’t solve the core problems of singletons, but it does mean you aren’t scattering singleton code over your app architecture, which means you can change your mind relatively easily if and when singletons start to be more pain than gain.
Even better, if your logging system is itself built on top of a protocol, you can create a DebugLogger
type that conforms to the same protocol and acts as a stub for testing – all the rest of your code that conforms to Logging
doesn’t have to change, but you get great testing quickly and easily.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
This article contains some content from my book Swift Design Patterns, where singletons are discussed in more detail alongside many other design patterns.
If you want to understand why various approaches are preferred over others as well as how to implement them in your own designs, Swift Design Patterns is the book for you.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.