設計模式學習-單例模式

敖老黑發表於2017-12-20

基本概念

單例模式是最常用的設計模式之一,能夠確保某個型別的物件在應用中只存在一個實力

單例模式的優點

  • 全域性只存在一個物件,便於管理

何時使用

當需要一個物件,又不想在整個應用範圍內賦值它(如印表機服務,音樂播放等)可以使用單例模式

  • 不可以用結果體等值型別實現。並且單例類不應該遵守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問題。

crash image

上面的問題解決辦法依舊很簡單 我們只需要給下面方法增加一個同步鎖。讓所有的非同步訪問變成同步即可解決

///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)
    }
}
複製程式碼

文中示例程式碼:github.com/RockyAo/Des…

相關文章