如何使 Swift 类单例实例线程安全?

Posted

技术标签:

【中文标题】如何使 Swift 类单例实例线程安全?【英文标题】:How to make a Swift Class Singleton instance thread safe? 【发布时间】:2017-04-13 13:26:50 【问题描述】:

我有一个 singleton 类:

class Database 
   static let instance:Database = Database()
   private var db: Connection?

   private init()
      do 
        db = try Connection("\(path)/SalesPresenterDatabase.sqlite3")
        catchprint(error)
   

现在我使用 Database.instance.xxxxxx 访问该类以在该类中执行一个功能。但是,当我从另一个线程访问该实例时,它会抛出奇怪的结果,就好像它试图创建另一个实例一样。我应该在同一个线程中引用实例吗?

为了澄清奇怪的结果,显示由于两个实例尝试同时访问数据库而导致的数据库 I/O 错误

更新 有关数据库代码的更多信息,请参阅此问题:Using transactions to insert is throwing errors Sqlite.swift

【问题讨论】:

Swift 单例创建是线程安全的。这并不意味着您在单例实例中调用的函数将神奇地线程安全。如果您需要使函数线程安全,则需要使用信号量、串行调度队列或操作队列之类的东西 However when I access the instance from another thread it throws bizarre results as if its trying to create another instance 我不这么认为,鉴于您的 Database 类的当前定义,您不能创建 2 个实例。但是,也许您没有显示该类中的更多代码。顺便说一句:对你的类的最后一个改进:你应该将它声明为 final 以防止子类化。 类中的附加代码显示了一个 addRow 函数。此函数在 alamofire 完成块中调用,然后在无法插入数据的地方抛出错误。有趣的是,如果我删除对我的 Database.instance 的所有调用。单例并让有问题的代码先运行它,它会正常工作... 有关数据库代码的更多信息,请参阅此问题:***.com/questions/43388443/… @RichardThompson 你可以使用 FMDB 并使用他的FMDatabaseQueue 线程安全地访问你的数据库 【参考方案1】:
class var shareInstance: ClassName 

    get 
        struct Static 
            static var instance: ClassName? = nil
            static var token: dispatch_once_t = 0
        
        dispatch_once(&Static.token, 
            Static.instance = ClassName()
        )
        return Static.instance!
    

使用:让 object:ClassName = ClassName.shareInstance

斯威夫特 3.0

class ClassName 
  static let sharedInstance: ClassName =  ClassName() ()

使用:让 object:ClassName = ClassName.shareInstance

【讨论】:

我使用的是 swift3,所以我无法使用 dispatch_once_t 选项....【参考方案2】:

在 Swift 3.0 中添加私有 init 以防止其他人使用默认 () 初始化程序。

class ClassName 
  static let sharedInstance = ClassName()
  private init()  //This prevents others from using the default '()' initializer for this class.

【讨论】:

但是,你认为创建 sharedInstance 是线程安全的吗?【参考方案3】:

单线程类。

final public class SettingsThreadSafe 

   public static let shared = SettingsThreadSafe()

   private let concurrentQueue = DispatchQueue(label: "com.appname.typeOfQueueAndUse", attributes: .concurrent)
   private var settings: [String: Any] = ["Theme": "Dark",
                                       "MaxConsurrentDownloads": 4]

   private init() 

   public func string(forKey key: String) -> String? 
       var result: String?
       concurrentQueue.sync 
           result = self.settings[key] as? String
       
       return result
   

   public func int(forKey key: String) -> Int? 
       var result: Int?
       concurrentQueue.sync 
           result = self.settings[key] as? Int
       
       return result
   

   public func set(value: Any, forKey key: String) 
       concurrentQueue.async( flags: .barrier ) 
           self.settings[key] = value
       
   

用于测试单例类的单元。

func testConcurrentUsage() 
    let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
    
    let expect = expectation(description: "Using SettingsThreadSafe.shared from multiple threads shall succeed")
    
    let callCount = 300
    for callIndex in 1...callCount 
        concurrentQueue.async 
            SettingsThreadSafe.shared.set(value: callIndex, forKey: String(callIndex))
        
    
    
    while SettingsThreadSafe.shared.int(forKey: String(callCount)) != callCount 
        // nop
    
    
    expect.fulfill()
    waitForExpectations(timeout: 5)  (error) in
        XCTAssertNil(error, "Test expectation failed")
    

【讨论】:

以上是关于如何使 Swift 类单例实例线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

C#中各版本的单例模式

单例模式

如何在 Java 中正确创建线程安全的单例工厂? [复制]

Controller是单例模式的吗?如何保证线程安全?

线程安全的懒汉式单例设计模式

Spring单例模式多线程安全问题-有状态的Bean