持久化

Tests Supported Xcode Versions codecov Documentation SwiftPM Compatible

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 开箱即用地支持

捕获错误

如果存储或转换器抛出错误,Persisterpersist(_:)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)
    }
}

转换器是类型安全的,例如,仅当要存储的值为 CodableStorage 支持 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 文件以获取完整许可证。