基本概念
單例模式是最常用的設計模式之一,能夠確保某個型別的物件在應用中只存在一個實力
單例模式的優點
- 全域性只存在一個物件,便於管理
何時使用
當需要一個物件,又不想在整個應用範圍內賦值它(如印表機服務,音樂播放等)可以使用單例模式
坑
- 不可以用結果體等值型別實現。並且單例類不應該遵守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)
}
}
複製程式碼