基本概念
单例模式是最常用的设计模式之一,能够确保某个类型的对象在应用中只存在一个实力
单例模式的优点
- 全局只存在一个对象,便于管理
何时使用
当需要一个对象,又不想在整个应用范围内赋值它(如打印机服务,音乐播放等)可以使用单例模式
坑
- 不可以用结果体等值类型实现。并且单例类不应该遵守NSCopying协议
- 并发问题,多线程中多条线程同时访问同意资源
简单示例
下面创建一个OSX命令行程序,并初始化以下代码。模拟服务器log输出
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()
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数组是可以在多条线程中访问的。增加以下代码测试在异步中会发生什么
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问题。

上面的问题解决办法依旧很简单 我们只需要给下面方法增加一个同步锁。让所有的异步访问变成同步即可解决
///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)
}
}