I was driving through the town of Singleton the other day and of course, it got me thinking about using singletons in my apps. Singletons were a commonly used pattern in Objective-C programming and appear in many of Apple’s own APIs, but seem to be increasingly frowned upon in the Swift world.
So what is a singleton?
A singleton is a class that only expects to have a single instance. Think of it as a global instance of a class. In some cases this makes perfect sense if there can only ever be one instance of a particular class or if there is a default variant that suits most cases e.g.
If you are using an object with a property name of “shared”, “standard” or “default” you can be pretty sure it is an singleton.
And what’s the problem with singletons?
There are probably many different opinions here but I have two thoughts about this:
- They are effectively global variables and global variables can make your code messy and un-predictable.
- If they can be accessed by multiple other objects, possibly at the same time, then you can get conflicts. These can be handled by clever use of background queues, but it isn’t easy.
What to use instead?
As I drove, I mused on a singleton that I had implemented recently. It was a logging utility that allowed any object in my app (mostly view controllers) to save a new entry to a log file. The basic structure of the Logger class was this:
Any object in my app could use the Logger like this:
When I got to think about how I was using this, I realised that instead of a Logger object that everything could use, what I really needed was a Loggable behaviour that I could apply & restrict to the few classes that actually needed to log events. For me, this was the break-through:
Create a behaviour, not an object.
As soon as I started thinking about this as a behaviour, a protocol became the obvious solution, so this is what I created:
We run immediately into one of the peculiarities of Swift protocol extensions which has been very well explained by Caesar Wirth. If I had declared
addToLog(_:) in the protocol, then any class or struct conforming to this protocol would have been free to over-write this function and provide its own version. This is not what I wanted here - I wanted every object to use the same version. So I left the function declaration out of the protocol definition and only included it in the protocol extension.
To use this behaviour, a class or struct just has to be declared as conforming to the Loggable protocol:
For my app, I knew that I would want all my NSViewControllers to be able to add log events, so instead of setting them all individually as conforming to the protocol, I used this shortcut which extends all NSViewControllers to conform to the protocol.
I added this line to the Loggable.swift file where I declared the protocol and its extension, but outside both of them.
Protocol-oriented programming is a new technique to me, so it really helps when I can find a practical example of where it solves a problem.
If you are new to POP, I highly recommend the Crusty talk from WWDC 2015. And this article by Matthijs Hollemans was invaluable to me in demonstrating the problems with object inheritance that are solved by protocols.