Act 0: Current situation

Im going to use a code snippet to give and example of what I’ve described above, simulating 3 singleton services and an example view controller.

This snippet is a real mess but is not an unusual situation for many iOS codebases. As you can see we have all 3 singletons used the example view controller, and some singletons are invoking the others, this situation makes our code untestable because we can’t mock anything.

Act 1: Introducing a Service Provider

First step to reduce singleton impact is the introduction of a Service Provider. To implement this pattern i’ll use Swift Protocol Extension, and thanks to this technique i’ll gain testability.

I’m writing a protocol with 3 services as variables and providing singleton instances in extension as default values.

Now any class or struct can just implement this protocol to get access to the services without invoking directly the shared singletons. We have just introduced a decoupling layer between our code and the singletons.

Here’s an example of how use it on the example view controller:

As you can imagine, now we can test the view controller simply adding an extension in our test target where a service var is pointing to a mock service:

Please remind that this kind of override works only if your singleton is a class because you can’t subclass a struct. If you are using struct singletons you’ll need to create generalize them with protocols.

Act 3: removing singleton static properties

At last we want to remove any reference to shared or sharedInstance static variables from our code.

To achieve this I’ll use a static factory that will return the same instance of the service class, removing any reference to singleton pattern from our code.

I also prefer to have this class with fileprivate access control inside Service Provider file, to prevent any access except service provider implementation.

Now you should have no shared static properties in your code and a strong decoupling layer between you services and the rest of your code.

Wrapping up

Decouple your singleton each others

Decouple your singletons and other components

Using protocol extension you’ll provide a default value that you can override in tests or in particular cases

Avoid exposing shared or sharedInstance global vars to avoid the temptation of using singletons again

What’s next

Here’s some hint to expand this approach and improve your solution:

Use protocol composition to have more specific service providers: