The recipe for Singletons removal

singleton

We all went through it, am I right? You join a new company, you jump on the new codebase, you find lots of singletons, get used to them, become friend with them, but after some time you realize it's time to terminate the friendship.

For a greater good.

And for better testing, of course.
And for proper dependency injection, of course.
And for your own sanity, of course.

After years in this field you should well know that singletons are bad, if you don't... well... argh, I have bad news for you πŸ™ƒ, but also a few articles, yes.

So here is a simple recipe to follow in order to have your app/code free from singletons.
To some of you, this could all seem very obvious, and you might also know that I don't write about obvious stuff, but tonight I'm tipsy and I had confirmation that most people are still unsure on how to get around such situations.

The recipe

  1. Make a list of the singletons in the app and the dependencies between them.

  2. Starting from the most high-level singleton (the one using the others more), apply 3-14 for each one of them.

  3. Create a property in every class that reference the singleton at least once

  4. Substitute all the references to the singleton with the reference to the property

  5. Create a lazy getter returning the singleton or assign the singleton to the property where more appropriate (in the viewDidLoad for ViewControllers or generally speaking the init methods)

  6. About the usages of singletons in static methods: refactor the code and turn those methods into instance methods as it surely is crap (this will allow dependency injection)

  7. About the usages of singletons in categories:

    • if you own the class, publicly expose the property in the class

    • if we class comes from a framework (e.g. UIViewController) refactor the code by removing the category (it was probably bad code since the beginning)

  8. About the usages of singletons in class methods:

    • you are simply doing it wrong, reconsider the design and remove the class methods
  9. Build the app and make sure it still behaves as expected

  10. Make sure the (unit|automation) tests are still green

  11. For every class using the singleton, modify it so that:

    • if the class is a ViewController, we prefer going for dependency setting rather that dependency injection as the view controller might be accessed via a segue (segue.destinationViewController). In this case, assert that the dependency is set in the viewDidLoad

    • otherwise, modify the designated initializer having it accepting a dependency via injection as per standard dependency injection. Keep the init chain correct (Obj-C & Swift)

  12. Modify the caller chain back up until the root object (most likely the AppDelegate) instantiates an instance of the original singleton and pass it down the chain

  13. Build the app and make sure it still behaves as expected

  14. Make sure the unit tests are still green

Show me the way

Really? Do I have to? Ok, let's do something quick.
Code is in Objective-C because singletons can be more easily found in legacy codebases... it is well-known that nobody writes singletons in Swift these days! Shots fired. 🎭

So here we have 2 singletons, one referencing the other. β†­

@implementation MySingleton

static MySingleton *sharedInstance = nil;

#pragma mark - Singleton

+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    
    return sharedInstance;
}

- (void)performCrazyWork
{
    [[MyOtherSingleton sharedInstance] evenCrazierWork];
}

@end
@implementation MyOtherSingleton

static MyOtherSingleton *sharedInstance = nil;

+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    
    return sharedInstance;
}

- (void)evenCrazierWork
{
    // I said, even crazier
}

@end

since MySingleton is the most-high level one, we start by addressing it.

Here is a service class using it:

@interface SomeService ()
@end

@implementation SomeService

- (void)method1
{
    [[MySingleton sharedInstance] performCrazyWork];
    // ...
}

- (void)method2
{
    // ...
    [[MySingleton sharedInstance] performCrazyWork];
}

@end

let's put it in a property

@interface SomeService ()

@property (nonatomic, strong) MySingleton *someDude;

@end

@implementation SomeService

- (instancetype)init
{
    self = [super init];
    if (self) {
        _someDude = [MySingleton sharedInstance];
    }
    return self;
}

- (void)method1
{
    [self.someDude performCrazyWork];
    // ...
}

- (void)method2
{
    // ...
    [self.someDude performCrazyWork];
}

@end

and then, one step further, use depedency injection

- (instancetype)initWithSomeDude:(MySingleton *)dude
{
    self = [super init];
    if (self) {
        _someDude = dude;
    }
    return self;
}

iterate and modify the callers up the chain.

Move to the next singleton in the list, in the example MyOtherSingleton:

@interface MySingleton ()

@property (nonatomic, strong) MyOtherSingleton *otherDude;

@end

@implementation MySingleton

- (instancetype)init
{
    self = [super init];
    if (self) {
        _otherDude = [MyOtherSingleton sharedInstance];
    }
    return self;
}

- (void)performCrazyWork
{
    [self.otherDude evenCrazierWork];
}

@end

You should have now taken the gist of it.

Extra points: in the end, put the dependencies behind protocols so that the unit testing would be easier. Or whatever.

Kill those shared instances! πŸ‘ΎπŸ‘ΎπŸ‘ΎπŸš€