设计模式学习-单例模式

基本概念

单例模式是最常用的设计模式之一,能够确保某个类型的对象在应用中只存在一个实力

单例模式的优点

  • 全局只存在一个对象,便于管理

何时使用

当需要一个对象,又不想在整个应用范围内赋值它(如打印机服务,音乐播放等)可以使用单例模式

  • 不可以用结果体等值类型实现。并且单例类不应该遵守NSCopying协议
  • 并发问题,多线程中多条线程同时访问同意资源

简单示例

下面创建一个OSX命令行程序,并初始化以下代码。模拟服务器log输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class DataItem {
enum ItemType : String {
case email = "Email address"
case phone = "Telephone number"
case card = "Credit Card Number"
}
var type:ItemType
var data:String
init(type:ItemType,data:String) {
self.type = type
self.data = data
}
}
final class BackupServer {
static let sharedServer:BackupServer = BackupServer(name: "MainBackupServer")
let name:String
private var data = [DataItem]()
private init(name:String) {
self.name = name
Logger.shared.log(msg: "Created new server named \(name)")
}
func backup(item:DataItem) {
data.append(item)
Logger.shared.log(msg: "backed up item of type \(item.type.rawValue)")
}
func getData() -> [DataItem] {
return data
}
}
final class Logger {
static let shared:Logger = Logger()
private var data:[String] = []
private init() { }
func log(msg:String) {
data.append(msg)
}
func printLog() {
data.forEach{
print("log: \($0)")
}
}
}
var server = BackupServer.sharedServer
server.backup(item: DataItem(type: .email, data: "542622608@qq.com"))
server.backup(item: DataItem(type: .phone, data: "186xxxx3146"))
Logger.shared.log(msg: "back up 2 items to \(server.name)")
var otherServer = BackupServer.sharedServer
otherServer.backup(item: DataItem(type: .email, data: "xxxx@gmail.com"))
Logger.shared.log(msg: "back up 1 item to \(otherServer.name)")
Logger.shared.printLog()
1
2
3
4
5
6
log: Created new server named MainBackupServer
log: backed up item of type Email address
log: backed up item of type Telephone number
log: back up 2 items to MainBackupServer
log: backed up item of type Email address
log: back up 1 item to MainBackupServer

上面代码是一个单例模式的简单实现。但是data数组是可以在多条线程中访问的。增加以下代码测试在异步中会发生什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
et group = DispatchGroup()
let queue = DispatchQueue(label: "workqueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
for count in 0..<100{
let workItem = DispatchWorkItem(block: {
BackupServer.sharedServer.backup(item: DataItem(type: .email, data: "test\(count)@example.com"))
})
queue.async(group: group, execute: workItem)
}
_ = group.wait(wallTimeout: DispatchWallTime.distantFuture)
print("\(server.getData().count) items were back up")

上面代码,会产生如下图所示的crash问题。

crash image

上面的问题解决办法依旧很简单 我们只需要给下面方法增加一个同步锁。让所有的异步访问变成同步即可解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
///BackupServer类加锁
func backup(item:DataItem) {
arrayQ.sync {
self.data.append(item)
Logger.shared.log(msg: "backed up item of type \(item.type.rawValue)")
}
}
///log类加锁
func log(msg:String) {
arrayQ.sync {
data.append(msg)
}
}

文中示例代码:https://github.com/RockyAo/DesignPatternerLearn