简单介绍单例模式
单例模式其实大家应该都耳熟能详了,至少在工作上会时不时的听到单例这个词。那么什么是单例模式,简单的讲,就是只有一个实例,可以在所有的地方调用。举个例子,你买了台PS4放在公司休闲区里,那么全公司的人,都可以去玩这台PS4,而且现实生活中的PS4并不能因为你new一个又多生成一个,那是没有意义的。
还有一个场景:有时候你想创建一个对象,并且让所有人都以一种简单一致的方式使用这个对象,这个时候用单例模式就会来的比较简单。举个例子:如果我们定义了一个logger.swift
的类,这个类的作用是捕获项目里所有的log日志,如果不使用单例模式,那么如果创建了两个logger对象,这两个输出的log日志是独立的,并不能满足我们的捕捉所有日志的需求。这就是所谓的 封装共享资源
。
实现单例模式
实现单例模式必须遵循的原则:
- 单例必须是该类
唯一
的实例 - 单例不能被另一个对象取代,不管是谁,都!不!行!
- 单例必须能让所有需要使用它的组件获取到
快速实现单例
在swift中可以通过使用 全部常量
,快速实现单例模式。
let globalLogger = Logger()
final class Logger {
private ini() {
// do something
}
func log(msg: String) {
// do something
}
}
使用Swift常量可以保证两点:
- 全局常量是
惰性初始化
的 - 这种惰性初始化是
线程安全
的
final
关键字可以防止子类创建
传统地实现单例
final class Foo {
... //some var and func
class var manager:Foo {
struct SingletonWrapper {
static let singleton = Foo();
}
return SingletonWrapper.singleton;
}
}
为什么要嵌套结构体,因为Swift不支持类存储属性
处理并发
当我们使用单例的时候,往往就会出现一个情况,就是当有多个组件调用同一个单例的时候,会造成单例中的变量是线程不安全的。也就是说,不能同时的对一个类似数组等变量进行操作。所以最好使用 串行队列
来保证线程安全。
private let serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
...
dispatch_sync(serialQueue, {() in
// do something
})
可能遇到的问题
代码文件共享
在创建单例和定义全局常量时,应该用关键字修饰将他们定义在单独的文件中,这样其他组件就无法违反单例的原则。
不使用并发保护
如果应用对共享的数据结构存在依赖,例如对数组或者全局函数存在依赖,我们就应该确保单例代码不会同时被多个线程访问。如果不确定是否应该采取并发保护,那就采取并发保护。因为我们宁愿多消耗一点串行访问所需的成本,也不想让应用崩溃。
拙劣的优化
其实,当并发保护出现性能问题时,应该优先考虑一下代码的设计是否合理。而不是埋怨像GCD这样的并发机制性能不佳,一般GCD就够用了,而且GCD简单,容易理解。
Tips
barrier block
var arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_CONCURRENT)
...
dispatch_barrier_async(arrayQ, {() in
// write data
})
...
dispatch_sync(arrayQ, {() in
// read data
})
为什么要这样做呢?
这样做可以区分读取数组内容的线程和修改数组内容的线程
将读操作放在普通的block里,将写操作放在了barrier block里。dispatch_barrier_async会向队列丢一个block,并且同时会改变block的执行方式,上面这个队列会先看前面有没有其他任务要执行,如果有,就等着,等到在他面前所有的任务都执行完了,它再执行。
也就是说,当它到达了队列最前端时,GCD会等待所有正在进行的读操作
完成,再进行写操作
。
再换句话说,使用barrier block会将并发队列
暂时变成串行队列
。这可以很方便的创建读/写锁
。
保护回调
上面的代码还有一个小问题,就是:如果我们在线程block里再丢一个block进去,like this:
var callback() -> Void
var arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_CONCURRENT)
init(callback -> Void) {
self.callback = callback
}
...
dispatch_barrier_async(arrayQ, {() in
// write data
self.callBack()
})
...
dispatch_sync(arrayQ, {() in
// read data
self.callback()
})
这里的callback也是很有可能被并发调用的,所以我们可以采取比较合适的做法,就是让组件在提供callback的时候自行说明是否这个回调block加了保护。
var callback() -> Void
var arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_CONCURRENT)
var callbackQ = dispatch_queue_create("callbackQ", DISPATCH_QUEUE_SERIAL)
init(callback -> Void, protect:Bool = true) {
self.callback = callback
if protect {
// 如果添加保护,则加入到串行队列中
self.callback = {() in
dispatch_sync(self.callbackQ, {() in
callback()
})
}
}
}
...
dispatch_barrier_async(arrayQ, {() in
// write data
self.callBack()
})
...
dispatch_sync(arrayQ, {() in
// read data
self.callback()
})