iOS Tipsswift

Swift 单例的实现与解析

2019-03-22  本文已影响0人  CrystalZhu

单例 Singleton 是设计模式中非常重要的一种,在 iOS 中也非常常见。在之前的面试过程中也被问到过单例相关的问题,当时感觉自己答得不是很好,后来也是又深入研究了一下。本文主要是简单分析一下单例,并且讨论了一下 Swift 中单例的实现。

Singleton 基本介绍

单例是什么?

单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。

224774cc266fde20949d85b98742f52c06695f1e47de-nzrxK4_fw658.jpeg 120404b42990bc7aa192ebf5ba0c525b40c8a1d8a96-veLGj8_fw658.jpeg

基本要求:

iOS 中的单例

单例的优点

Singleton 在 Swift 中的实现

第一种方式:

也是最直接简洁的方式:将实例定义为全局变量。比如下面的代码,声明了一个实例变量sharedManager

let sharedManager = MyManager(string: someString)
class MyManager {
    // Properties
    let string: String
    // Initialization
    init(string: String) {
        self.string = string
    }
}复制代码

而如果将上述实例变量在全局命名区(global namespace)第一次调用,由于Swift中全局变量是懒加载(lazy initialize)。所以,在application(_:didFinishLaunchingWithOptions:)中调用的时候之后,shardManager会在AppDelegate类中被初始化,之后程序中所有调用sharedManager实例的地方将都使用该实例。

另外,Swift 全局变量初始化时默认使用dispatch_once,这保证了全局变量的构造器(initializer)只会被调用一次,保证了shardManager原子性

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // 初始化位置,以及使用方式
    print(sharedManager)
    return true
}复制代码

Initialize lazily, run the initializer for a global the first time it is referenced, similar to Java. It allows custom initializers, startup time in Swift scales cleanly with no global initializers to slow it down, and the order of execution is completely predictable.
Swift 采用与 Java 类似的方式,对全局变量实行懒加载。这样设计使得构造器可以自定义、启动时间不会因为加载全局变量而变慢、同时操作执行的顺序也变得可控。

The lazy initializer for a global variable ... is launched as dispatch_onceto make sure that the initialization is atomic. This enables a cool way to use dispatch_oncein your code: just declare a global variable with an initializer and mark it private.
全局变量的懒加载在初始化时会使用 dispatch_once以确保初始化的原子性。所以这是一个很酷地使用 dispatch_once的方式:仅在定义全局变量时将其构造器标志为 private就行。

第二种方式:

Swift 2 开始增加了static关键字,用于限定变量的作用域。如果不使用static(比如let string),那么每一个MyManager实例中均有一个string变量。而使用static之后,shared成为全局变量,成为单例。

另外可以注意到,由于构造器使用了 private关键字,所以也保证了单例的原子性。

class MyManager {
    // 全局变量
    static let shared = MyManager(string: someString)

    // Properties
    let string: String
    // Initialization
    private init(string: String) {
        self.string = string
    }
}复制代码

第二种方式的使用如下。可以看出采用第二种方式实现单例,代码的可读性增加了,能够直观的分辨出这是一个单例。

// 使用
print(MyManager.shared)复制代码

第三种方式:

第三种方式是第二种方式的变种,更加复杂。让单例在闭包(Closure)中初始化,同时加入类方法来获取单例。

class MyManager {
    // 全局变量
    private static let sharedManager: MyManager = {
        let shared = MyManager(string: someString) 
        // 可以做一些其他的配置
        // ...
        return shared
    }()
    // Properties
    let string: String
    // Initialization
    private init(string: String) {
        self.string = string
    }
    // Accessors
    class func shared() -> MyManager {
        return sharedManager
    }
}复制代码

可以看出第三种方式虽然更加复杂,但是可以在闭包中作一些额外的配置。同时,调用单例的方式也不一样,需要调用单例中的类方法shared()

print(MyManager.shared())复制代码

单例的缺陷

单例状态的混乱

由于单例是共享的,所以当使用单例时,程序员无法清楚的知道单例当前的状态。

当用户登录,由一个实例负责当前用户的各项操作。但是由于共享,当前用户的状态很可能已经被其他实例改变,而原来的实例仍然不知道这项改变。如果想要解决这个问题,实例就必须对单例的状态进行监控。Notifications 是一种方式,但是这样会使程序过于复杂,同时产生很多无谓的通知。

测试困难

测试困难主要是由于单例状态的混乱而造成的。因为单例的状态可以被其他共享的实例所修改,所以进行需要依赖单例的测试时,很难从一个干净、清晰的状态开始每一个 test case

单例访问的混乱

由于单例时全局的,所以无法对访问权限作出限定。程序任何位置、任何实例都可以对单例进行访问,这将容易造成管理上的混乱。

参考资料

转载自:https://juejin.im/post/59e30701f265da432f3026ad

上一篇 下一篇

猜你喜欢

热点阅读