swift基础之实现单例模式(仿NSUserDefaults)

单例模式是设计模式中最简单的一种,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象。

当你只需要一个实例的时候需要使用单例,如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

2024906-83e51e4709c4420f.jpg

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声明为局部变量,其实只能说然并卵,没什么实际用处。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值