如何使 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 类单例实例线程安全?的主要内容,如果未能解决你的问题,请参考以下文章