Swift中编写单例的正确方式
作者:AppleDev 发布时间:[ 2016/8/18 11:16:03 ] 推荐标签:软件测试 测试用例
在之前的帖子里聊过状态管理有多痛苦,有时这是不可避免的。一个状态管理的例子大家都很熟悉,那是单例。使用Swift时,有许多方法实现单例,这是个麻烦事,因为我们不知道哪个合适。这里我们来回顾一下单例的历史,看一看在Swift中如何正确地实现单例。
如果你想直接看看Swift中单例的正确实现方式,直接跳到帖子后即可。
往事回忆之ObjC单例
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
在这个现成方案中,我们可以看到单例的基本结构。让我们来约定一些规则,这样便于更好的理解。
单例规则
关于单例,有三个重要的准则需要牢记:
1、单例必须是的(要不怎么叫单例?) 在程序生命周期中只能存在一个这样的实例。单例的存在使我们可以全局访问状态。例如:
NSNotificationCenter, UIApplication和NSUserDefaults。
2、为保证单例的性,单例类的初始化方法必须是私有的。这样可以避免其他对象通过单例类创建额外的实例。
3、考虑到规则1,为保证在整个程序的生命周期中值有一个实例被创建,单例必须是线程安全的。并发有时候确实挺复杂,简单说来,如果单例的代码不正确,如果有两个线程同时实例化一个单例对象,可能会创建出两个单例对象。也是说,必须保证单例的线程安全性,才可以保证其性。通过调用dispatch_once,即可保证实例化代码只运行一次。
在程序中保持单例的性,只初始化一次,这样并不难。帖子的余下部分中,需要记住:单例实现要满足隐藏的dispatch_once规则。
Swift单例
自Swift 1.0开始,创建单例有很多种方法。这些链接中已经有很详尽的描述,比如
· SwiftSingleton
· dispatch_once singleton model in swift
· Apple Swift Blog
但是谁喜欢点链接呢?先剧透一下吧:总共有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代码块中是安全的。但是对于静态类变量来说,情况又如何?
这个问题带我们到更激动人心的思考中去:
正确的方法(也即是“单行单例法”)现在已经被证明正确。
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
}
到此为止,我们已经做了许多研究工作。这个帖子的灵感来源于我们在Capital One的一次对话:结对编程review代码的过程中,我们试图找到在App中使用Swift编写正确、一致的单例方法。我们知道编写单例的正确方法,但是无法用理论来证明。没有足够的文档支持,想证明方法的正确是徒劳的。在网上或博客圈中没有足够多的信息的话,这只能是一家之言,大家都知道如果网上查不到信息,不会相信。这点让我很难过。
我搜索了许多信息,甚至翻到了google搜索结果的10多页,还是一无所获。难道没有人发帖证明单行单利方法的正确性?可能有人发过,但是太难被发现了。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11