1

I am reading a lot about the Singleton Pattern. I am currently using it to store groups of global state in my first app. I am reaching a point where I wonder which approach to implement API client classes and similar with.

Are Structs with static vars and static functions having the same issues?

To illustrate what I mean, I've tried to write the same heavily simplified and exact same(?) scenario twice.

1. A singleton being worked with by a view controller:

struct APIClientSingletonClass {

    static let shared = APIClientSingletonClass()

    var stateOfSomehting: Bool = true
    var stateOfSomehtingMore: Bool = false
    var stateNumber: CGFloat = 1234
    var stateOfSomehtingComputed: CGFloat {
        return stateNumber * 10
    }

    func convertSomethingToSomethingElse() {
        // calling method in self like this:
        otherMethod()
    }
    func otherMethod() {
        // doing stuff here
    }
}



// Calling APIClient from outside:
class ViewControllerTalkingToSingleton: UIViewController {

    var api = APIClientSingletonClass.shared

    override func viewDidLoad() {
        super.viewDidLoad()
        api.convertSomethingToSomethingElse()
        api.stateOfSomehting = false
    }
}

2. Another approach:

struct APIClientStruct {

    static var stateOfSomehting: Bool = true
    static var stateOfSomehtingMore: Bool = false
    static var stateNumber: CGFloat = 1234
    static var stateOfSomehtingComputed: CGFloat {
        return stateNumber * 10
    }

    static func convertSomethingToSomethingElse() {
        // calling method in self like this:
        APIClientStruct.otherMethod()
    }

    static func otherMethod() {
        // doing stuff here
    }
}


// Calling APIClient from outside:
class ViewControllerTalkingToStruct: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        APIClientStruct.convertSomethingToSomethingElse()
        APIClientStruct.stateOfSomehting = false
    }
}

What do you guys think? Is approach 2 falling into the same traps that seem to make Singletons such a double-edged sword?

Any input is really appreciated! Best from Berlin

EDIT: This thread is pretty interesting, but I'm not sure it really relates to my question:

Difference between static class and singleton pattern?

Since there are many perspectives on this topic, let me specify: Does my approach 2 have the same problem implications with testing and code maintainability?

HelloTimo
  • 558
  • 1
  • 5
  • 18

3 Answers3

3

What generally makes sinlgetons hard to test is that the singleton objects are typically always accessed directly . Because of this, you don't have a means to substitute the real singleton object (e.g. a data-store that's backed by a database) with a mock object for testing (e.g. a data-store that's backed by an easily-configurable array of predefined test data).

Using static members has the same fundamental issue. When referencing a static member directly, you don't have a means of substituting a mock object in place of the real prod implementation.

The solution to this is quite simple: don't access singleton members directly. What I do is something like this:

// An example of a dependency.
protocol DataAccessLayer {
    func getData() -> [Int]
}

// A real implementation of DataAccessLayer, backed by a real production database
class ProdDB: DataAccessLayer {
    static let instance = ProdDB()
    private init() {}

    func getData() -> [Int] {
        return [1, 2, 3] // pretend this actually queries a DB
    }
}

// A mcok implementation of DataAccessLayer, made for simple testing using mock data, without involving a production database.
class MockDB: DataAccessLayer {
    func getData() -> [Int] {
        return [1, 2, 3] // The mock *actually* hardcodes this data
    }
}


// A protocol that stores all databases and services used throughout your app
protocol ServiceContextProtocol {
    var dataAccessLayer: DataAccessLayer { get } // Present via protocol, either real impl or mock can go here
    //var fooAPIGateway: FooAPIGateway { get }
    //... add all other databases and services here
}

// The real service context, containing real databases and service gateways
class ProdServiceContext: ServiceContextProtocol {
    let dataAccessLayer: DataAccessLayer = ProdDB.instance
    //var fooAPIGateway: ProdFooAPIGateway { get }
    //... add all other prod databases and services here
}

// A mock service context, used in testing, which provides mocked databases and service gatways
class MockServiceContext: ServiceContextProtocol {
    let dataAccessLayer: DataAccessLayer  = MockDB()
    //var fooAPIGateway: MockFooAPIGateway { get }
    //... add all other mock databases and services here
}

let debug = false // Set this true when you're running in a test context

// A global variable through which you access all other global state (databases, services, etc.)
let ServiceContext: ServiceContextProtocol = debug ? MockServiceContext() : ProdServiceContext()

// Always reference ServiceContext.dataAccessLayer, ServiceContext.fooAPIGateway, etc.
// and *never* reference ProdDB.instance of MockDB directly.
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Cool, protocol-based seems like a good idea - from what I understand so far. Gonna try it. Thanks! – HelloTimo Oct 18 '18 at 18:09
  • 1
    @HelloTimo A common superclass is also feasible, but not as common. What's important, in either case, is that the interface of your dependencies is separated from the dependencies themselves, allowing you to pick which implementation serves your need best (prod, beta, mock, etc.) – Alexander Oct 18 '18 at 19:10
3

A class-based singleton is the way to go, provided you accommodate for dependency injection for your tests. The way to do this is to create a single singleton for your app, called, say, DependencyManager. In your AppDelegate (or from other classes if needed), you'd create whatever controllers, network services, realm models, etc you want to hang on your DependencyManager, and then assign them to the DependencyManager. This code would be skipped by your unit tests.

Your unit tests can then access the DependencyManager (and thus instantiate the DependencyManager during first access), and populate it with mock versions of those controllers and services to whatever degree each unit test desires.

Your UIViewControllers, your MVVM view models, etc... can access the DependencyManager as a singleton, and thus get either the real controllers and services, or a mock version of them, depending on if you're running the app or unit tests.

If you're doing MVVM, I also recommend that when a UIViewController is about to create its view model class, that it first checks a special property in the DependencyManager to see if a mockViewModel exists. A single property can serve this purpose, as only one of your UIViewControllers ever would be tested at once. It'd use that property instead of creating a new view model for itself. In this way, you can mock your view models when testing each UIViewController. (There's other tricks involved to being able to prop up a single UIViewController for testing, but I won't cover that here).

Note that all of the above can work very nicely with an app that also wants to use storyboards and/or nibs. People are so down on storyboards because they can't figure out how to do dependency injection of mock services for their view controllers. Well, the above is the solution! Just make sure in your AppDelegate to load the storyboard AFTER setting up the DependencyManager. (Remove the storyboard name from your info.plist, and instantiate it yourself in AppDelegate).

I've written a few shipped apps this way, as well as some sample apps for an SDK, along with the tests. I highly recommend the approach! And be sure to write your unit tests and viewController tests either during or at least immediately after development of each such class, or you'll never get around to them!

Smartcat
  • 2,834
  • 1
  • 13
  • 25
  • Cool, thanks a lot. Taking @Alexander ´s answer into consideration I think I get the point in regards to unit tests: indirect access of the Singletons. Either way. I´ll check both. Sounds very good! – HelloTimo Oct 18 '18 at 18:55
  • 1
    @HelloTimo feel free to contact me via the [iOS Developers](https://ios-developers.io/) slack if you want to have a deeper conversation about this approach and implementation. – Smartcat Oct 18 '18 at 18:59
  • Thanks guys, difficult to decide which answer is more helpful. The more I spend time with this topic, the complex it gets... – HelloTimo Oct 19 '18 at 07:54
  • have you seen this talk by Stephen / pointfree? vimeo.com/291588126 mentioned above by Dinsen. Seems to be a similar approach he's presenting?! – HelloTimo Oct 19 '18 at 08:20
  • I hadn't seen it but thank you for pointing it out! I had developed the above approach in fall of 2017 after much searching, and this is the first video or article I've seen that meshes with what I'd come up with. He's absolutely right about the benefits of self-documented dependencies to a class via this approach, since all that passing along of dependencies to children is no longer needed. Thanks! – Smartcat Oct 21 '18 at 02:38
0

I would use a Class based Singleton. Just remember the 2 criteria for having a singleton. You want GLOBAL ACCESS and SINGLE INSTANCE in your program. There is a couple problems where struct based singleton would fail. Once you assign a struct to a new variable, Swift makes a complete copy under the hood.

Another useful snip of information can be found using this link.

What's the difference between Struct based and Class based singletons?

Jae Yang
  • 479
  • 3
  • 13