Why I’m Moving Away From Singletons in Swift

Cas Hoefman

May 18, 2025

For a long time, the Singleton pattern has been a common solution for managing shared state across an application. It’s simple, intuitive, and easy to implement. However, as apps grow in complexity, Singletons often introduce more problems than they solve.

In my recent project, Themeable, I initially used Singletons to manage themes. While practical at first, it quickly became clear that relying heavily on Singletons was limiting and potentially harmful in the long run.

The Problem With Singletons

Here’s why I decided Singletons might not be the best solution for scalable, maintainable apps:

  1. Tight Coupling
    Singletons make your components depend directly on a global instance, complicating reuse, testing, and maintenance.
  2. Global State Issues
    Having state accessible globally can cause unpredictable behavior and hidden dependencies, making debugging difficult.
  3. Testing Challenges
    Singletons are notoriously challenging to mock or stub in unit tests, making your tests less reliable and harder to write.
  4. Concurrency Problems
    In multi-threaded or asynchronous environments, managing a global instance safely becomes increasingly complex and error-prone.

Enter Dependency Injection

The modern solution preferred by many Swift developers, including myself, is Dependency Injection (DI). DI involves explicitly passing dependencies to components rather than them pulling global state directly.

Benefits of DI include:

  • Improved Testability: Easier to mock dependencies.
  • Loose Coupling: Components become modular and easier to maintain.
  • Better Clarity: Dependencies become explicit rather than hidden.
  • Enhanced Flexibility: Easier to swap or alter implementations without affecting other parts of the app.

Practical Example

Here’s a quick example of transitioning away from a Singleton-based theme manager to DI:

Singleton-based approach:

// Using a Singleton
let theme = ThemeManager.shared.currentTheme

DI-based approach:

// Injected dependency
class ViewModel {
    private let themeManager: ThemeManager

    init(themeManager: ThemeManager) {
        self.themeManager = themeManager
    }

    func currentTheme() -> ThemeProtocol {
        themeManager.currentTheme
    }
}

The DI approach clearly states what the class depends on, making it more maintainable, testable, and robust.

My Recommendation

While Singletons might be easy at first glance, adopting DI greatly improves your app architecture. After my experience with Themeable and Fresco, I’ve learned firsthand that embracing Dependency Injection results in cleaner, more maintainable, and more scalable codebases.

Looking Forward

I’m excited to apply these modern Swift development practices in my projects moving forward. Stay tuned as I continue sharing more architecture insights and practical development tips!

What do you think? Have you made the transition from Singletons to DI? I’d love to hear your experiences!

Happy coding!

Leave a Comment