TruMan1
TruMan1

Reputation: 36178

Alternative to smelly global variables in Swift?

I've defined a global struct with static properties with values I use in many of my view controllers, like this:

public struct AppGlobal {
    static var currentUser = UserModel()
    static let someManager = SomeManager()

    // Prevent others from initializing
    private init() { }
}

Then in my UIViewController, I can do something like this:

class MyController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        AppGlobal.currentUser.prop1 = "abc123"
        AppGlobal.someManager.startUpdating()
    }
}

This is obviously very convenient, but smells really bad. I believe dependency injection would come in handy here, but not sure how. Is there a more elegant alternative to creating the AppGlobal singleton properties?

Upvotes: 2

Views: 1609

Answers (2)

tgyhlsb
tgyhlsb

Reputation: 1915

If this question is about global or not, you should see this thread : What is so bad about singletons?

But if you want a better design for your implementation of a singleton you can try something like this :

class SingletonExample: NSObject {

    static let sharedInstance: SingletonExample()
}

class OtherSingletonExample: NSObject {

    static let sharedInstance: OtherSingletonExample()
}

Then you can use SingletonExample.sharedInstance and OtherSingletonExample.sharedInstance anywhere in your code.

The idea is to isolate one singleton from another and access it as a class attribute instead of creating a big global struct for anything.

Upvotes: 3

vikingosegundo
vikingosegundo

Reputation: 52237

I can't see why you need to access userModel or someManager through a global state (and yes — Singletons are just that).

Why not just set it where you need it?

"Dependency Injection" is a 25-dollar term for a 5-cent concept. That's not to say that it's a bad term…
[…]
Dependency injection means giving an object its instance variables. Really. That's it.

– James Shore: Dependency Injection Demystified

Either do it during constructing

class C {
    let currentUser: UserModel
    let someManager: SomeManager
    init(currentUser:UserModel, someManger:SomeManager) {
        self.currentUser = currentUser
        self.someManager = someManager
    }
}

or through properties. If you need to make sure that all properties are set, do something like this:

class MyController: UIViewController {
    var currentUser: UserModel? {
        didSet{
            self.configureIfPossible()
        }
    }
    var someManager: SomeManager?{
        didSet{
            self.configureIfPossible()
        }
    }

    func configureIfPossible(){
        if let currentUser = self.currentUser, someManager = self.someManager {
            // configure    
        }
    }
}

In my current project we have the policy that every dependency must be visible and configurable from outside the class.

An example:

class LibrarySegmentViewController: BaseContentViewController {
    var userDefaults: NSUserDefaults?
    var previousSorting : LibrarySortingOrder = .AZ
    var sorting : LibrarySortingOrder {
        set{
            self.previousSorting = sorting
            if let filterMode = self.filterMode {
                self.userDefaults?.setInteger(newValue.rawValue, forKey: "\(filterMode)_LibrarySorting")
            }
            self.setupIfReady()
        }
        get{
            if let filterMode = self.filterMode {
                if let s = LibrarySortingOrder(rawValue: self.userDefaults!.integerForKey("\(filterMode)_LibrarySorting")) {
                    return s
                }
            }
            return .Date
        }
    }

}

So as you can see, we even use properties to reference NSUserDefaults.standardUserDefaults(). We do this as we can pass in fresh instances during testing, without bigger mocking hassle.

And this is the most importing reason why not to use singletons directly: The dependencies are hidden and might bite you during testing and refactoring. Another example would be an API client singleton that is hidden in the code and performs unwanted networking requests during testing. If it is set from outside of the tested class you can just pass in a mocked networking client that does not perform any requests but returns test data.

So even if you use singletons, you should pass it in as a dependencies.

Upvotes: 5

Related Questions