The iOS development community is clearly split on the issue of singletons.
On one hand, they are largely considered an anti-pattern in software development. Singletons create implicit dependencies and global state that can wreak havoc as code bases grow.
On the other hand, they’re pervasive. You’ll find them everywhere, from open source components to Apple’s own frameworks.
So despite singletons being so maligned, why does Apple use so many? Why do we see so many on iOS? And how do we get rid of them once they’re there?
Fruit Company singletons
It’s clear to see the singleton pattern is present everywhere in Apple’s own frameworks. Running a quick search in Dash on the iOS 10 docs reveals at least 18 different singletons. From UIApplication to the current audio session, you’d be hard-pressed to develop an application without coming across a Apple-approved singleton.
But if singletons are so terrible, why does Apple use them? 🤔
At their essence, singletons are used for representing resources of which there can only be one.
Are you running the single UIApplication? Yes. Are you using the single accelerometer on the device? Yup. Are you using the one and only Photo library? Indeed you are.
The facilities that Apple models as singletons truly are singletons. That is to say, they don’t do it out of convenience, they do it because it makes sense.
Curse of convenience
Ok, but why do we use them everywhere?
I’m convinced that the reason we see so many singletons in libraries and frameworks is because there’s a major issue in how iOS is taught: we’re never told what goes between the AppDelegate and our chains of UIViewControllers.
For example, let’s say you have an object that handles the current user in your app. And lets assume you have many controllers that care about the state of the currently logged on user. Are you really going to pass this object from UIViewController to UIViewController? If you have a navigation flow that’s many screens deep, this’ll get annoying fast. Wouldn’t it just be easier to create a singleton and access it wherever?
Of course it would.
It’s true that when you don’t have a good place for these things, singletons are really convenient. However, using singletons here is a terrible band-aid over a much larger issue. We must interpret this as a problem in our architecture.
The negative side-effects have been well documented. But in my opinion, the worst effect they can have on your code is that they make you lazy. Since you can access these objects from anywhere, it’s all too easy to lose sight of important aspects of your architecture and separation of concerns. And then, before you know it, they get out of control. Bugs and race conditions litter your app.
And then it’s time to refactor 🙃
Refactoring Singletons
Ok, now what do you do if your app is already littered with singletons? Well, the way out isn’t complicated, but we need to roll up our sleeves and do some dirty work. Here’s how we would go about it, in a large codebase.
1- Choose a set of screens to work on. Ideally, any flow that’s contained within a single navigation controller is a good candidate. 2- Create a simple coordinator object for that flow. This is the object that will live between the AppDelegate and our controllers. It’ll give us a place to move code outside of our view controller. 3- Move all navigation code from inside these UIViewControllers to the coordinator. Hopefully this will be enough to decouple each of view controller from the rest. Using delegation is a clean way to achieve this. 4- Inside your UIViewController, replace calls to singletons with calls to a property of the same type. 5- Instantiate the singletons in your coordinator object and use dependency injection to pass it to the UIViewControllers that need them. 6- Repeat the process with all the different flows in your app. Use dependency injection between your different coordinators whenever necessary. Soon enough, all those singletons will bubble up to where they’re really supposed to live.
But why the singleton slaughter?
Following the steps above, you’ll see how easy it becomes to move functionality out of your View Controllers. They’ll become smaller and more focused.
You’ll also set yourself up for testing, if that’s something you might like to do in the future. Dependency injection is an essential tool in making components testable. In my opinion, it’s a prime habit to pick up, even if you’re not going to test at all!
Finally, you’ll notice that your dependencies now become explicit. It’ll be easy to tell what a given view controller needs to work, and when it is doing too much. If a view controller is doing too much, you’ll see the warning signs earlier, allowing you to make the better decisions for the health of your controllers and your code base.
Wrapping up!
Singletons are a great for when you’re learning the ropes of the platform. However, I truly believe that we can all grow by putting away the training wheels and graduating to a better way of doing things.
I hope you found this article insightful 🙂 Together we saw why singletons should be avoided, and why Apple uses them all the time despite this.
We also saw how to go about refactoring singletons and give them a proper home in your application.
Finally we touched on why going through this whole process is a good practice, in terms of your app’s internal health, as well as in terms of your growth as a developer.
If you’ve ever been through the immense fun of refactoring out singletons, I’d love to hear your war story in the comments!
And until next time, Happy coding ✌🏻