GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

How to implement singletons in Swift the smart way

Are they a design pattern or a design anti-pattern?

Paul Hudson       @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.

Hacking with Swift is sponsored by RevenueCat.

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.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

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.

Hacking with Swift is sponsored by RevenueCat.

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.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.4/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.