【问题标题】:How to make a Swift Class Singleton instance thread safe?如何使 Swift 类单例实例线程安全?
【发布时间】: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")
        }catch{print(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 的所有调用。单例并让有问题的代码先运行它,它会正常工作...
  • 有关数据库代码的更多信息,请参阅此问题:stackoverflow.com/questions/43388443/…
  • @RichardThompson 你可以使用 FMDB 并使用他的FMDatabaseQueue 线程安全地访问你的数据库

标签: ios swift swift3 singleton


【解决方案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")
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    • 2018-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多