设计模式学习-单例模式

237 阅读2分钟

基本概念

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

单例模式的优点

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

何时使用

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

  • 不可以用结果体等值类型实现。并且单例类不应该遵守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…