替代Swift中的臭味全局变量?

时间:2016-02-19 16:19:09

标签: ios swift dependency-injection swift2 global-variables

我已经定义了一个带有静态属性的全局结构,其中包含我在许多视图控制器中使用的值,如下所示:

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

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

然后在我的UIViewController中,我可以做这样的事情:

class MyController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

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

这显然非常方便,但闻起来很糟糕。我相信依赖注入会在这里派上用场,但不确定如何。是否有更优雅的替代方法来创建AppGlobal单例属性?

2 个答案:

答案 0 :(得分:5)

我无法理解为什么你需要通过全局状态访问userModel或someManager(是的 - 单身就是这样)。

为什么不把它设置在你需要的地方?

  对于5美分概念,“依赖注入”是一个25美元的术语。   这并不是说这是一个坏词......   [...]
  依赖注入意味着   给对象一个实例变量。真。就是这样。

     

- James Shore:Dependency Injection Demystified

在构建

期间执行此操作
class C {
    let currentUser: UserModel
    let someManager: SomeManager
    init(currentUser:UserModel, someManger:SomeManager) {
        self.currentUser = currentUser
        self.someManager = someManager
    }
}

或通过属性。如果您需要确保设置了所有属性,请执行以下操作:

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    
        }
    }
}

在我当前的项目中,我们的策略是每个依赖项必须是可见的,并且可以从类外部进行配置。

一个例子:

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
        }
    }

}

正如您所看到的,我们甚至使用属性来引用NSUserDefaults.standardUserDefaults()。我们这样做是因为我们可以在测试期间传递新的实例,而不会有更大的嘲弄麻烦。

这是不直接使用单例的最重要原因:依赖项是隐藏的,可能会在测试和重构期间咬你。另一个例子是API客户端单例,它隐藏在代码中并在测试期间执行不需要的网络请求。如果它是从测试类的外部设置的,您只需传入一个模拟的网络客户端,该客户端不执行任何请求但返回测试数据。

因此,即使您使用单例,也应该将其作为依赖项传递。

答案 1 :(得分:3)

如果这个问题与全局有关,你应该看到这个帖子: What is so bad about singletons?

但是如果你想要一个更好的设计来实现单例,你可以尝试这样的事情:

class SingletonExample: NSObject {

    static let sharedInstance: SingletonExample()
}

class OtherSingletonExample: NSObject {

    static let sharedInstance: OtherSingletonExample()
}

然后,您可以在代码中的任何位置使用SingletonExample.sharedInstanceOtherSingletonExample.sharedInstance

这个想法是将一个单例与另一个单独隔离,并将其作为类属性访问,而不是为任何东西创建一个大的全局结构。