单例模式是设计模式中最简单的一种,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象。
当你只需要一个实例的时候需要使用单例,如UIApplication.sharedApplication()
等 ,windows的任务管理器,回收站 都是只能同时存在一个。
下面看看swift中的几种实现方式:
1、
import Foundation
class SingleOne {
//单例
static let shareSingleOne = SingleOne()
}
一句话搞定,静态常理。所有地方用到的都是同一个
2、
import Foundation
class SingleTwo {
//单例
class func shareSingleTwo()->SingleTwo{
struct Singleton{
static var onceToken : dispatch_once_t = 0
static var single:SingleTwo?
}
dispatch_once(&Singleton.onceToken,{
Singleton.single=shareSingleTwo()
}
)
return Singleton.single!
}
}
使用dispatch_once可以保证其中的代码只执行一次
3、
import Foundation
//全局的常量
let single = SingleThree()
class SingleThree {
class var sharedInstance : SingleThree {
return single
}
}
4、
import Foundation
class SingleFour {
static var sharedInstance : SingleFour {
struct Static {
static let instance : SingleFour = SingleFour()
}
return Static.instance
}
}
在方法内定义静态常量
下面为NSUserdefaults的运用
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var preferenceSwitch: UISwitch!
@IBAction func savePreferenceState(sender: AnyObject) {
let defaults = NSUserDefaults.standardUserDefaults()
if preferenceSwitch.on {
defaults.setBool(true, forKey: "SwitchState")
} else {
defaults.setBool(false, forKey: "SwitchState")
}
defaults.synchronize();
}
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.objectForKey("SwitchState") != nil) {
preferenceSwitch.on = defaults.boolForKey("SwitchState")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
关于一些dispatch_once的一些坑
参考来源:http://www.cocoachina.com/swift/20160531/16523.html
1. 初识dispatch_once_t与dispatch_once( )
1.1 dispatch_once_t
在苹果官方文档中,是这么定义dispatch_once_t的:
1
2
3
4
5
6
7
8
|
/*!
* @typedef dispatch_once_t
*
* @abstract
* A predicate for use with dispatch_once(). It must be initialized to zero.
* Note: static and global variables default to zero.
*/
public typealias dispatch_once_t = Int
|
从上面我们可以得到以下3个信息:
-
dispatch_once_t是Int的一个别名
-
dispatch_once_t 用来当做dispatch_once()的断言(predicate)来用的
-
dispatch_once_t必须初始化为0
同时末尾还有一个提示:static和global variables的默认值为0。
这个提示该怎么理解呢?在我们声明一个变量为可选值的时候,若没有为其赋值,其默认值为nil,比如:
1
2
|
var
aString: String?
print(aString)
|
打印的结果为nil
于是我就声明了一个全局(global)的dispatch_once_t,然后打印它:
1
2
|
var
aToken: dispatch_once_t
print(aToken)
|
结果不好的事情发生了,系统给报了一个错误:
1
|
error: variable
'aToken'
used before being initialized
|
aToken在使用之前必须初始化,说好的默认为0呢?难道是说默认设为0?
1.2 dispatch_once( )
dispatch_once( )在苹果官方文档中是这样描述的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/*!
* @function dispatch_once
*
* @abstract
* Execute a block once and only once.
*
* @param predicate
* A pointer to a dispatch_once_t that is used to test whether the block has
* completed or not.
*
* @param block
* The block to execute once.
*
* @discussion
* Always call dispatch_once() before using or testing any variables that are
* initialized by the block.
*/
@available(iOS 4.0, *)
public func dispatch_once(predicate: UnsafeMutablePointer<dispatch_once_t>, _ block: dispatch_block_t)</dispatch_once_t>
|
从上面我们可以看出: dispatch_once()的作用:执行一段代码块,并且只执行一次。
dispatch_once()有两个参数:
参数1:predicate,一个指向dispatch_once_t的指针,用来测试block是否执行完成
参数2:block,要执行的代码块
1.3 dispatch_once()工作原理
重点来了,系统是怎么根据predicate指针来判断block是否执行完成的呢?
-
首先,predicate指针所指向的变量的类型为dispatch_once_t,而dispatch_once_t变量初始化时必须设置为0
-
dispatch_once()在进行predicate(断言)判定时,dispatch_once_t变量的值只有为0时,才会执行block,因此初次调用时,block会被调用一次
-
当block执行完毕后,系统会将dispatch_once_t变量的值置为-1,因此再次调用dispatch_once()的时候,就会跳过block
Tips:dispatch_once_t是一个只能为0或-1的Int值,默认为0,执行完block,被置为-1。若置为其他值,会报错EXC_BAD_ACCESS。
2. dispatch_once实践
2.1 global测试
1
2
3
4
5
6
7
8
9
10
11
|
// global测试
var
globalToken: dispatch_once_t = 0
func calledOnce() {
print(globalToken)
dispatch_once(&globalToken) {
print(
"Called once in global scope."
)
}
}
calledOnce()
calledOnce()
calledOnce()
|
globalToken的类型必须设置为变量,因为系统在调用完block之后,会对其进行修改,设为let编译器会报如下错误:
1
|
error: cannot pass immutable value as inout argument:
'globalToken'
is a
'let'
|
执行结果为:
1
2
3
4
|
0
Called once
in
global scope.
-1
-1
|
我们可以看到,block只执行了一次,globalToken的值也被系统修改为-1。
2.2 static测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// static测试
class MyClass {
private static
var
staticToken: dispatch_once_t = 0
class func doItOnce() {
print(staticToken)
dispatch_once(&staticToken) {
print(
"Called in static scope"
)
}
}
}
class MyClassSub: MyClass {
override static func doItOnce(){
super
.doItOnce()
print(
"called in sub class"
)
}
}
MyClass.doItOnce()
MyClass.doItOnce()
MyClassSub.doItOnce()
MyClassSub.doItOnce()
|
执行结果如下:
1
2
3
4
5
6
7
8
|
0
Called
in
static scope
-1
// 下面为子类调用结果
-1
called
in
sub class
-1
called
in
sub class
|
从执行结果我们可以看出,无论是父类调用,还是子类调用,block只执行一次
Tips:声明类的类型方法有两个关键字可供选择:static和class;使用class关键字声明,子类可以重写父类方法,而使用static关键字声明,子类则不能重写父类方法,否则会报如下错误:error: class method overrides a 'final' class method
2.3 实例测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 实例测试
class InstanceTest {
private
var
instanceToken: dispatch_once_t = 0
func doItOncePerInstance() {
dispatch_once(&instanceToken) {
print(
"Called once per instance"
)
}
}
}
let instance1 = InstanceTest()
instance1.doItOncePerInstance()
instance1.doItOncePerInstance()
let instance2 = InstanceTest()
instance2.doItOncePerInstance()
instance2.doItOncePerInstance()
|
执行结果如下:
1
2
3
4
|
// instance1调用
Called once per instance
// instance2调用
Called once per instance
|
2.4 local测试
1
2
3
4
5
6
7
8
9
10
11
|
class LocalTest {
func doItOnce() {
var
localToken: dispatch_once_t = 0
dispatch_once(&localToken) {
print(
"Called every time"
)
}
}
}
let localTest = LocalTest()
localTest.doItOnce()
localTest.doItOnce()
|
测试结果为:
1
2
|
Called every time
Called every time
|
函数每次调用,block都会执行。原因在于localToken为局部变量,每次调用函数时,都会将其初始化为0 。
3.小结
在苹果的官方文档中,dispach_once_t的Discussion是这么描述的:
Variables of this type must have global or static scope. The result of using this type with automatic or dynamic allocation is undefined.
dispatch_once_t类型的变量必须声明为全局变量或者static 类型的变量,只有如此,block才会执行且只执行一次。在Objcet-C中,我们经常通过使用static声明dispatch_once_t变量来创建单例,也是基于此。通过上面的global测试和static测试,我们也验证了这一点。
使用动态分配的方法创建dispatch_once_t类型的变量,结果是不确定的。在实例测试中,我们可以看到,每个实例各执行了一次,但在单个实例的生命周期中,只会执行一次。有时候,我们必须在一些会被多次调用的函数中做一些配置,而实际需求是配置一次即可。然后呢?你懂的。至于将dispatch_once_t声明为局部变量,其实只能说然并卵,没什么实际用处。