Persist
是一个框架,旨在帮助持久化和检索值,并支持诸如以 JSON 数据形式存储的转换。
Persist 提供了 Persister
类,该类可用于从各种形式的存储中持久化和检索值。
Persisted
属性包装器封装了 Persister
,使其可以轻松拥有一个自动持久化其值的属性。
class Foo {
enum Bar: Int {
case firstBar = 1
case secondBar = 2
}
@Persisted(key: "foo-bar", userDefaults: .standard, transformer: RawRepresentableTransformer())
var bar: Bar = .firstBar
@Persisted(key: "foo-baz", userDefaults: .standard)
var baz: String?
}
let foo = Foo()
foo.bar // "Bar.firstBar"
foo.bar = .secondBar
UserDefaults.standard.object(forKey: "foo-bar") // 2
foo.baz // nil
foo.baz = "new-value"
UserDefaults.standard.object(forKey: "foo-baz") // "new-value"
Persist
开箱即用地支持
UserDefaults
NSUbiquitousKeyValueStore
FileManager
InMemoryStorage
(字典的简单包装器)如果存储或转换器抛出错误,Persister
的 persist(_:)
和 retrieveValueOrThrow()
函数将会抛出异常。
Persisted
封装了 Persister
并将其作为 projectedValue
公开,这允许您捕获错误
class Foo {
@Persisted(key: "foo-bar", userDefaults: .standard)
var bar: String?
}
do {
let foo = Foo()
try foo.$bar.persist("new-value")
try foo.$bar.retrieveValueOrThrow()
} catch {
// Something went wrong
}
当目标为 macOS 10.15、iOS 13、tvOS 13 或 watchOS 6 或更高版本时,可以使用 Combine 来订阅更新
class Foo {
@Persisted(key: "foo-bar", userDefaults: .standard)
var bar: String?
}
let foo = Foo()
let subscription = foo.$bar.updatesPublisher.sink { result in
switch result {
case .success(let update):
print("New value:", update.newValue)
switch update.event {
case .persisted(let newValue):
print("Value updated to:", newValue)
// `update.newValue` will be new value
case .removed:
print("Value was deleted")
// `update.newValue` will be default value
}
case .failure(let error):
print("Error occurred retrieving value after update:", error)
}
}
对于 macOS 10.15、iOS 13、tvOS 13 或 watchOS 6 之前的版本,提供了闭包 API
class Foo {
@Persisted(key: "foo-bar", userDefaults: .standard)
var bar: String?
}
let foo = Foo()
let subscription = foo.$bar.addUpdateListener() { result in
switch result {
case .success(let update):
print("New value:", update.newValue)
switch update.event {
case .persisted(let newValue):
print("Value updated to:", newValue)
// `update.newValue` will be new value
case .removed:
print("Value was deleted")
// `update.newValue` will be default value
}
case .failure(let error):
print("Error occurred retrieving value after update:", error)
}
}
某些存储方法可能仅支持类型子集,或者您可能想要修改某些值的编码/解码方式(例如,以确保磁盘上的日期表示与 API 发送/期望的相同)。这就是转换器的用武之地
struct Bar: Codable {
var baz: String
}
class Foo {
@Persisted(key: "bar", userDefaults: .standard, transformer: JSONTransformer())
var bar: Bar?
}
let foo = Foo()
let subscription = foo.$bar.addUpdateListener() { result in
switch result {
case .success(let update):
// `update.newValue` is a `Bar?`
print("New value:", update.newValue)
switch update.event {
case .persisted(let bar):
// `bar` is the decoded `Bar`
print("Value updated to:", bar)
case .removed:
print("Value was deleted")
}
case .failure(let error):
print("Error occurred retrieving value after update:", error)
}
}
转换器是类型安全的,例如,仅当要存储的值为 Codable
且 Storage
支持 Data
时,JSONTransformer
才可用。
如果一个值应该经过多个转换器,您可以将它们链接起来。
struct Bar: Codable {
var baz: String
}
public struct BarTransformer: Transformer {
public func transformValue(_ bar: Bar) -> Bar {
var bar = bar
bar.baz = "transformed"
return bar
}
public func untransformValue(_ bar: Bar) -> Bar {
return bar
}
}
class Foo {
@Persisted(key: "bar", userDefaults: .standard, transformer: BarTransformer().append(JSONTransformer()))
var bar: Bar?
}
let foo = Foo()
let bar = Bar(baz: "example value")
foo.bar = bar
foo.bar.baz // "transformed"
可以提供一个默认值,当持久化器返回 nil
或抛出错误时将使用该值。
struct Foo {
@Persisted(key: "bar", userDefaults: .standard)
var bar = "default"
}
var foo = Foo()
foo.bar // "default"
当作为 defaultValue
参数提供时,该值在首次需要时会被延迟评估。
func makeUUID() -> UUID {
print("Making UUID")
return UUID()
}
struct Foo {
@Persisted(key: "bar", userDefaults: .standard, defaultValue: makeUUID())
var bar: UUID
}
/**
This would not print anything because the default value is never required.
*/
var foo = Foo()
foo.bar = UUID()
/**
This would print "Making UUID" once.
*/
var foo = Foo()
let firstCall = foo.bar
let secondCall = foo.bar
firstCall == secondCall // true
默认值可以在使用时选择性地存储,无论是由于错误还是因为存储返回 nil
。当第一个值是随机的并且在初始创建后应在应用程序启动之间持久化时,这可能很有用。
struct Foo {
@Persisted(key: "persistedWhenNilInt", userDefaults: .standard, defaultValue: Int.random(in: 1...10), defaultValuePersistBehaviour: .persistWhenNil)
var persistedWhenNilInt: Int!
@Persisted(key: "notPersistedWhenNilInt", userDefaults: .standard, defaultValue: Int.random(in: 1...10))
var notPersistedWhenNilInt: Int!
}
var foo = Foo()
UserDefaults.standard.object(forKey: "persistedWhenNilInt") // nil
foo.persistedWhenNilInt // 3
UserDefaults.standard.object(forKey: "persistedWhenNilInt") // 3
foo.persistedWhenNilInt // 3
UserDefaults.standard.object(forKey: "notPersistedWhenNilInt") // nil
foo.notPersistedWhenNilInt // 7
UserDefaults.standard.object(forKey: "notPersistedWhenNilInt") // nil
foo.notPersistedWhenNilInt // 7
// ...restart app
UserDefaults.standard.object(forKey: "persistedWhenNilInt") // 3
foo.persistedWhenNilInt // 3
UserDefaults.standard.object(forKey: "notPersistedWhenNilInt") // nil
foo.notPersistedWhenNilInt // 4
为了支持依赖注入或初始化更复杂的 Persisted
实例,您可以在自己的 init 函数中初始化属性包装器
class Foo {
@Persisted
var bar: String?
init(userDefaults: UserDefaults) {
_bar = Persisted(key: "foo-bar", userDefaults: userDefaults)
}
}
可以通过 SwiftPM 安装 Persist,方法是将软件包添加到依赖项部分并作为目标的依赖项
let package = Package(
...
dependencies: [
.package(url: "https://github.com/JosephDuffy/Persist.git", from: "1.0.0"),
],
targets: [
.target(name: "MyApp", dependencies: ["Persist"]),
],
...
)
该项目根据 MIT 许可证发布。查看 LICENSE 文件以获取完整许可证。