本文由CocoaChina译者leon(社区ID)翻译自krakendev
原文:THE RIGHT WAY TO WRITE A SINGLETON
转载请保持全部内容和连接的完整性。php
在以前的帖子里聊过状态管理有多痛苦,有时这是不可避免的。一个状态管理的例子你们都很熟悉,那就是单例。使用Swift时,有许多方法实现单例,这是个麻烦事,由于咱们不知道哪一个最合适。这里咱们来回顾一下单例的历史,看一看在Swift中如何正确地实现单例。html
若是你想直接看看Swift中单例的正确实现方式,直接跳到帖子最后便可。git
往事回忆之ObjC单例github
Swift是Objective-C的一种天然演变,它用以下的方式实现单例:编程
@interface Kraken : NSObject @end @implementation Kraken + (instancetype)sharedInstance { static Kraken *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[Kraken alloc] init]; }); return sharedInstance; } @end
在这个现成方案中,咱们能够看到单例的基本结构。让咱们来约定一些规则,这样便于更好的理解。swift
单例规则api
关于单例,有三个重要的准则须要牢记:安全
1. 单例必须是惟一的(要不怎么叫单例?) 在程序生命周期中只能存在一个这样的实例。单例的存在使咱们能够全局访问状态。例如:并发
NSNotificationCenter, UIApplication和NSUserDefaults。app
2. 为保证单例的惟一性,单例类的初始化方法必须是私有的。这样就能够避免其余对象经过单例类建立额外的实例。
3. 考虑到规则1,为保证在整个程序的生命周期中值有一个实例被建立,单例必须是线程安全的。并发有时候确实挺复杂,简单说来,若是单例的代码不正确,若是有两个线程同时实例化一个单例对象,就可能会建立出两个单例对象。也就是说,必须保证单例的线程安全性,才能够保证其惟一性。经过调用dispatch_once,便可保证明例化代码只运行一次。
在程序中保持单例的惟一性,只初始化一次,这样并不难。帖子的余下部分中,须要记住:单例实现要知足隐藏的dispatch_once规则。
Swift单例
自Swift 1.0开始,建立单例有不少种方法。这些连接中已经有很详尽的描述,好比
https://github.com/hpique/SwiftSingleton,http://stackoverflow.com/questions/24024549/dispatch-once-singleton-model-in-swift和
https://developer.apple.com/swift/blog/?id=7。可是谁喜欢点连接呢?先剧透一下吧:总共有4个版本。咱们来清点一下:
1. 最丑陋方法(Swift皮,Objective-C心)
class TheOneAndOnlyKraken { 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. 结构体方法(“新瓶装老酒)
class TheOneAndOnlyKraken { class var sharedInstance: TheOneAndOnlyKraken { struct Static { static let instance = TheOneAndOnlyKraken() } return Static.instance } }
Swift 1.0时,不支持静态类变量,那时这个方法是不得已而为之。但使用结构体,就能够支持这个功能。由于静态变量的限制,咱们被约束在这样的一个模型中。这比Objective-C移植版本好一些,但还不够好。有趣的是,在Swift 1.2发布几个月后,我还能够看到这种写法。在那以后,反而更多了。
3.全局变量方法(“单行单例”方法)
private let sharedKraken = TheOneAndOnlyKraken() class TheOneAndOnlyKraken { 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 members of structs and enums) is run the first time that global is accessed, and is launched 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 a global variable with an initializer and mark it private.”)
这就是Apple官方文档给咱们的全部信息,但这些已经足够证实全局变量和结构体/枚举体的静态成员是支持”dispatch_once”特性的。如今,咱们相信使用全局变量来“懒包装”单例的初始化方法到dispatch_once代码块中是100%安全的。可是对于静态类变量来讲,状况又如何?
这个问题带咱们到更激动人心的思考中去:
正确的方法(也便是“单行单例法”)如今已经被证实正确。
class TheOneAndOnlyKraken { static let sharedInstance = TheOneAndOnlyKraken() }
到此为止,咱们已经作了许多研究工做。这个帖子的灵感来源于咱们在Capital One的一次对话:结对编程review代码的过程当中,咱们试图找到在App中使用Swift编写正确、一致的单例方法。咱们知道编写单例的正确方法,可是没法用理论来证实。没有足够的文档支持,想证实方法的正确是徒劳的。在网上或博客圈中没有足够多的信息的话,这只能是一家之言,你们都知道若是网上查不到信息,就不会相信。这点让我很难过。
我搜索了许多信息,甚至翻到了google搜索结果的10多页,仍是一无所得。难道没有人发帖证实单行单利方法的正确性?可能有人发过,可是太难被发现了。
所以我决定将各类单例都写一变,而后在运行时加入断点来观测。
分析了每一个stack trace的记录后,我发现了有趣的东西——证据!
来看看截图:
使用全局单例方法
使用单行单例方法
第一张图片展现了使用全局实例时的stack trace。标红的地方须要注意。在调用Kraken单例以前,先调用了swift_once,接下来是swift_once_block_invoke。Apple以前在文档中已经说过,“懒实例化”的全局变量会被自动放在dispatch_once块中,咱们能够假定说的就是这个东西。
了解了这些知识,咱们来看看漂亮的单行单例方法。如图所示,调用彻底同样。这样,咱们就有了证据证实单行单例方法是正确的。
不要忘记设置初始化方法为私有
@davedelong,Apple的Framework传道者,善意地提醒我:必须保证init方法的私有性,只有这样,才能保证单例是真正惟一的,避免外部对象经过访问init方法建立单例类的其余实例。因为Swift中的全部对象都是由公共的初始化方法建立的,咱们须要重写本身的init方法,并设置其为私有的。这很简单,并且不会破坏到咱们优雅的单行单例方法。
class TheOneAndOnlyKraken { static let sharedInstance = TheOneAndOnlyKraken() private init() {} //This prevents others from using the default '()' initializer for this class. }
这样作就能够保证编译器在某个类尝试使用()来初始化TheOneAndOnlyKraken时,抛出错误:
就是这样,咱们的单行单例,很是完美!
结论
这里回复一下jtbandes在“top rated answer to swift singletons on Stack Overflow”这个帖子中的问题:我也找不到哪里有文档证实let语句能够带来线程安全性的好处。我记得在去年参加WWDC的时候有相似的说法,没办法保证读者或各位Googler也偶遇到这个说法。但愿这个帖子能帮助你们理解为何单行单例在Swift中是正确的方法。