键值存储

UserDefaults 的类型安全、可观察和可注入的包装器。

简单示例

  1. 定义您要保存的键和类型。
struct AppKeys: KeyGroup {
    let launchCount = KeyDefinition(key: "launchCount", defaultValue: 0)
    let lastLaunchDate = KeyDefinition<Date?>(key: "lastLaunchDate")
}
  1. 创建一个存储并通过 @dynamicMemberLookup 读取/写入值
let storage = KeyValueStorage<AppKeys>(backend: UserDefaults.standard)

// Read
let launchCount = storage.launchCount 

// Write
storage.launchCount = launchCount + 1
strorage.lastLaunchDate = .now

概念

KeyValueStorage 基于以下三个概念开发

  1. 类型安全: 您可以类型安全地读取和写入常见类型(如 IntString)以及您的自定义类型。
  2. 可注入的后端: 您可以轻松地将存储值的后端存储更改为任何 UserDefaults InMemoryStorage
  3. 可观察的变化: KeyValueStorage 支持 Observation、AsyncSequence 和 Combine 的 Publisher。

类型安全

键定义

如上节所示,在键组中定义键使您的代码类型安全。

struct AppKeys: KeyGroup {
    let launchCount = KeyDefinition(key: "launchCount", defaultValue: 0)
    let lastLaunchDate = KeyDefinition<Date?>(key: "lastLaunchDate")
}

您可以将 UserDefaults 可以接受的所有类型指定为 KeyDefinition 的类型。如果像 lastLaunchDate 一样为 KeyDefinition 的类型指定 Optional,则可以省略默认值。

您可以读取和写入值。

let storage = KeyValueStorage<AppKeys>(backend: UserDefaults.standard)
let lastLaunchDate: Int = storage.lastLaunchDate
storage.lastLaunchDate = lastLaunchDate + 1

自定义类型支持

您可以通过使类型符合 KeyValueStorageValue 来存储和读取您的自定义类型。

如果您的类型是 RawRepresentable,则只需添加一致性。

enum Fruit: Int, KeyValueStorageValue {
    case apple
    case banana
    case orange
}

struct AppKeys: KeyGroup {
    let fruit = KeyDefinition<Fruit>(key: "fruit", defaultValue: .apple)
}

let storage = KeyValueStorage<AppKeys>(backend: UserDefaults.standard)
let fruit: Fruit = storage.fruit

在其他情况下,您可以编写自定义序列化/反序列化逻辑。

struct Person: KeyValueStorageValue, Equatable {
    typealias StoredRawValue = [String: any Sendable]

    var name: String
    var age: Int

    func serialize() -> StoredRawValue {
        ["name": name, "age": age]
    }

    static func deserialize(from dictionary: StoredRawValue) -> Person? {
        guard let name = dictionary["name"] as? String,
              let age = dictionary["age"] as? Int
        else { return nil }
        return Person(name: name, age: age)
    }
}

struct AppKeys: KeyGroup {
    let person = KeyDefinition<Person?>(key: "person")
}

let storage = KeyValueStorage<AppKeys>(backend: UserDefaults.standard)
let person: Person? = storage.person

Codable 支持 (JSON)

此外,您可以使用 JSONKeyDefinition 轻松存储继承 Codable 的类型。

struct Account: Codable {
    var name: String
    var email: String
}

struct AppKeys: KeyGroup {
    let account = JSONKeyDefinition<Account?>(key: "account")
}

let storage = KeyValueStorage<AppKeys>(backend: UserDefaults.standard)
let account: Account? = storage.account

键组

KeyGroup 是键的组合,并且同一组中的所有键都确保存储在同一存储中。

并且,该组可以嵌套在另一个组中。

因此,例如,您可以按目的划分键并将它们组合成一个组。

struct AppKeys: KeyGroup {
    let launchCount = KeyDefinition(key: "launchCount", defaultValue: 0)
    let debug = DebugKeys()
}

struct DebugKeys: KeyGroup {
    let showConsole = KeyDefinition<Bool>(key: "showConsole", defaultValue: false)
}

let standardStorage = KeyValueStorage<AppKeys>(backend: UserDefaults.standard)
let launchCount = standardStorage.launchCount
let showConsole = standardStorage.debug.showConsole

可注入的后端

您可以轻松地将存储值的后端更改为任何 UserDefaults

let standardStorage = KeyValueStorage<StandardKeys>(backend: UserDefaults.standard)
let appGroupStorage = KeyValueStorage<AppGroupKeys>(backend: UserDefaults(suiteName: "APP_GROUP")!)

InMemoryStorage 也可用作后端。
当您想并行运行单元测试时,这很有用。

let standardStorage = KeyValueStorage<StandardKeys>(backend: InMemoryStorage())

可观察的变化

观察

默认情况下,KeyValueStorage 支持 Observation。

例如,当计数器更新时,此视图会自动更新。

struct Keys: KeyGroup {
    let counter = KeyDefinition(key: "counter", defaultValue: 0)
}

struct ContentView: View {
    var storage: KeyValueStorage<Keys>

    var body: some View {
        VStack {
            Text("\(storage.counter)")
            Button("add") {
                storage.counter += 1
            }
        }
    }
}

注意

请捕获 KeyValueStorage,只要您需要观察它,因为当 KeyValueStorage 释放时,观察就结束了。

AsyncSequence

您可以通过 AsyncSequence 观察键的变化

let storage: KeyValueStorage<Keys> = ...
Task {
    for await _ in storage.stream(key: \.counter) {
        print("New value: \(storage.counter)")
    }
}

注意

请捕获 KeyValueStorage,只要您需要观察它,因为当 KeyValueStorage 释放时,流就结束了。

Combine

您可以通过 AsyncSequence 观察键的变化

let storage: KeyValueStorage<Keys> = ...
storage.publishers(key: \.counter)
    .sink {
        print("New value: \(storage.counter)")
    }

注意

请捕获 KeyValueStorage,只要您需要观察它,因为当 KeyValueStorage 释放时,流就结束了。

要求

Swift 6+

平台

安装

您可以通过 Swift Package Manager 添加此软件包。

dependencies: [
    .package(url: "https://github.com/mtj0928/key-value-storage", from: "0.3.0")
],
targets: [
    .target(name: "YOUR_TARGETS", dependencies: [
      .product(name: "KeyValueStorage", package: "key-value-storage")
    ]),
]

文档

包括几篇文章的文档可在此处获取:此处