程序猿必知的swift单例编写的正确方式

来源:互联网 发布:百度大数据预测双色球 编辑:程序博客网 时间:2024/06/06 15:47

 

在使用swift编程语言进行iOS应用开发的时候,我们常常借助单例来进行状态管理,但由于实现单例的方法很多,问题就来了,哪个才是最合适的呢?今天小编和大家分享的就是swift中编写单例的正确方式,一起来看看吧。

 

单例规则

 

关于单例,我们首先需要牢记三个重要的准则:

 

1. 单例必须是唯一的, 在程序生命周期中只能存在一个这样的实例。单例的存在使我们可以全局访问状态。例如:

NSNotificationCenter,UIApplication和NSUserDefaults。

 

2. 为保证单例的唯一性,单例类的初始化方法必须是私有的。这样就可以避免其他对象通过单例类创建额外的实例。

 

3. 考虑到规则1,为保证在整个程序的生命周期中值有一个实例被创建,单例必须是线程安全的。并发有时候确实挺复杂,简单说来,如果单例的代码不正确,如果有两个线程同时实例化一个单例对象,就可能会创建出两个单例对象。也就是说,必须保证单例的线程安全性,才可以保证其唯一性。通过调用dispatch_once,即可保证实例化代码只运行一次。

 

Swift单例

 

自Swift 1.0开始,创建单例有很多种方法,但最主要的就是下面4种:

 

1. 最丑陋方法(Swift皮,Objective-C心)

classTheOneAndOnlyKraken {

    class var sharedInstance:TheOneAndOnlyKraken {

        struct Static {

            static var onceToken:dispatch_once_t = 0

            static var instance:TheOneAndOnlyKraken? = nil

        }

        dispatch_once(&Static.onceToken) {

            Static.instance =TheOneAndOnlyKraken()

        }

        return Static.instance!

    }

}

这种方法是Objective-C的直接移植版,不好看是,因为Swift本该更简洁、更有描述力。

 

 

2. 结构体方法

classTheOneAndOnlyKraken {

    class var sharedInstance:TheOneAndOnlyKraken {

        struct Static {

            static let instance =TheOneAndOnlyKraken()

        }

        return Static.instance

    }

}

 

Swift 1.0时,不支持静态类变量,那时这个方法是不得已而为之。但使用结构体,就可以支持这个功能。因为静态变量的限制,我们被约束在这样的一个模型中。这比Objective-C移植版本好一些,但还不够好。

 

3.全局变量方法

private letsharedKraken = TheOneAndOnlyKraken()

classTheOneAndOnlyKraken {

    class var sharedInstance:TheOneAndOnlyKraken {

        return sharedKraken

    }

}

 

在Swift 1.2以后,我们有了访问权限设置(access control specifiers) 的功能和静态类成员(static class members)。这意味着我们终于可以摆脱混乱的全局变量、全局命名空间,也不会发生命名空间冲突了。这个版本看起来更Swiftier一点。

 

现在,你可能会有疑问:为何看不到dispatch_once?根据Apple Swift博客中的说法,以上方法都自动满足dispatch_once规则。这里有个帖子可以证明dispatch_once规则一直在起作用。

 

“全局变量(还有结构体和枚举体的静态成员)的Lazy初始化方法会在其被访问的时候调用一次。类似于调用'dispatch_once'以保证其初始化的原子性。这样就有了一种很酷的'单次调用'方式:只声明一个全局变量和私有的初始化方法即可。”--来自Apple's Swift Blog

 

(“The lazy initializer for a global variable (also for static membersof structs and enums) is run the first time that global is accessed, and islaunched as `dispatch_once` to make sure that the initialization is atomic.This enables a cool way to use `dispatch_once` in your code: just declare aglobal variable with an initializer and mark it private.”)

这就是Apple官方文档给我们的所有信息,但这些已经足够证明全局变量和结构体/枚举体的静态成员是支持”dispatch_once”特性的。现在,我们相信使用全局变量来“懒包装”单例的初始化方法到dispatch_once代码块中是100%安全的。但是对于静态类变量来说,情况又如何?

 

这个问题带我们到更激动人心的思考中去:

 

正确的方法现在已经被证明正确。

 

classTheOneAndOnlyKraken {

    static let sharedInstance =TheOneAndOnlyKraken()

}

 

到此为止,我们已经做了许多研究工作。这个帖子的灵感来源于我们在Capital One的一次对话:结对编程review代码的过程中,我们试图找到在App中使用Swift编写正确、一致的单例方法。我们知道编写单例的正确方法,但是无法用理论来证明。没有足够的文档支持,想证明方法的正确是徒劳的。在网上或博客圈中没有足够多的信息的话,这只能是一家之言,大家都知道如果网上查不到信息,就不会相信。

 

分析了每个stack trace的记录后,我发现:

 

使用全局单例方法

 

 

使用单行单例方法

 

第一张图片展示了使用全局实例时的stacktrace。标红的地方需要注意。在调用Kraken单例之前,先调用了swift_once,接下来是swift_once_block_invoke。Apple之前在文档中已经说过,“懒实例化”的全局变量会被自动放在dispatch_once块中,我们可以假定说的就是这个东西。

 

了解了这些知识,我们来看看漂亮的单行单例方法。如图所示,调用完全一样。这样,我们就有了证据证明单行单例方法是正确的。

 

不要忘记设置初始化方法为私有

 

必须保证init方法的私有性,只有这样,才能保证单例是真正唯一的,避免外部对象通过访问init方法创建单例类的其他实例。由于Swift中的所有对象都是由公共的初始化方法创建的,我们需要重写自己的init方法,并设置其为私有的。这很简单,而且不会破坏到我们优雅的单行单例方法。

 

class TheOneAndOnlyKraken{

    static let sharedInstance =TheOneAndOnlyKraken()

    private init() {} //This prevents othersfrom using the default '()' initializer for this class.

}

 

这样做就可以保证编译器在某个类尝试使用()来初始化TheOneAndOnlyKraken时,抛出错误:

 

 

 

 

结束语

 

     希望通过本文的分享,能帮助大家在Swift中找到编写单例的正确方式,同时也帮助大家更好的理解,为什么单行单例在swift中是正确的。

 


相关文章:《Swift 2.0构造函数是如何实现继承的?

 

0 0
原创粉丝点击