VAPOR 3: Learn Server-Side Swift with hands-on projects >>

How to implement singletons in Swift the smart way

Paul Hudson    April 28th 2018    @twostraws

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:

  • They are only created on first access, so it’s possible they might never be created.
  • You can funnel users through a getter/setter method that lets you track access for debugging purposes.
  • They make it easier to change your app architecture if you later move away from singletons – just change from your shared instance to a new instance.

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.

Where next?

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.

 

MASTER SWIFT NOW
Buy Pro Swift Buy Swift Design Patterns Buy Practical iOS 11 Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>