我试图了解我的代码是否真的是线程安全的。对
storeData
的读/写应该是,但是 sync
中对 didSet
的调用也是线程安全的吗?
另外,是否有更好的做法来在参与者中加载初始数据?在构造函数中初始化
Task
感觉很奇怪。
actor Store<Element: Codable>: StoreType {
private let fileName: String
private var storeData: [Element]? {
didSet {
sync()
}
}
init(fileName: String) {
self.fileName = fileName
Task {
await loadFromPlist()
}
}
func read() async throws -> [Element]? {
storeData
}
func write(_ value: [Element]) async throws {
storeData = value
}
func remove() async throws {
storeData = nil
}
}
private extension Store {
func loadFromPlist() {
let decoder = PropertyListDecoder()
guard let data = try? Data.init(contentsOf: plistURL),
let elements = try? decoder.decode([Element].self, from: data) else {
return
}
storeData = elements
}
private func sync() {
let encoder = PropertyListEncoder()
guard let data = try? encoder.encode(storeData) else {
return
}
if FileManager.default.fileExists(atPath: plistURL.path) {
try? data.write(to: plistURL)
} else {
FileManager.default.createFile(atPath: plistURL.path, contents: data, attributes: nil)
}
}
var plistURL: URL {
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return documents.appendingPathComponent("\(fileName).plist")
}
}
这段代码是“线程安全的”,因为在每一点,
storeData
都是一个有效的 Swift 值。您永远不会因低级数据竞争而崩溃。读取 storeData
将始终返回一个包含“某物”的数组。并且此代码永远不会损坏 plist 文件。
这段代码不是“线程安全的”,因为在初始化过程中肯定会出现令人惊讶的情况。
let store = Store<String>(fileName: "x")
let oldValues = try await store.read()
// What is oldValues? Might be empty. Might be the contents of the file.
不保证您的
init
任务将在 read
之前或之后运行。所以你不知道那里会发生什么。
由于读取文件本质上是一个可能会失败的异步事件,因此我建议使用异步和抛出标记您的
init
。那么你就不需要任务了,一切都有意义了。并且没有理由使 read()
异步或抛出。
或者,您可以延迟加载文件,直到
read()
,当前是异步的并会抛出异常。我会使用Optional来跟踪数据是否已加载,而不是在这里返回Optional。