Foil Actions Status

一个轻量级的 属性包装器,用于以正确的方式处理 UserDefaults


关于

阅读这篇文章:编写 UserDefaults 属性包装器的更好方法

为什么叫这个名字?

Foil,就像“让我快速轻松地用锡箔纸 包裹存储这些剩下的食物,以便我稍后可以食用。” 🌯 😉

Foil(锡箔纸):
名词
北美
一种非常薄、柔软、易撕裂的铝片,用于烹饪、包装、化妆品和绝缘。

用法

您可以将 @FoilDefaultStorage 用于非可选值,将 @FoilDefaultStorageOptional 用于可选值。您可能希望将所有用户默认设置存储在一个地方,但这不是必须的。任何类型上的任何属性都可以使用此包装器。

final class AppSettings {
    static let shared = AppSettings()

    @FoilDefaultStorage(key: "flagEnabled")
    var flagEnabled = true

    @FoilDefaultStorage(key: "totalCount")
    var totalCount = 0

    @FoilDefaultStorageOptional(key: "timestamp")
    var timestamp: Date?
}

// Usage

func userDidToggleSetting(_ sender: UISwitch) {
    AppSettings.shared.flagEnabled = sender.isOn
}

还包含一个示例应用程序项目。

使用 enum

如果您喜欢使用 enum 作为键,则编写特定于您应用程序的扩展很容易。 但是,这不是必需的。 事实上,除非您有特殊的理由引用键,否则这完全没有必要。

enum AppSettingsKey: String, CaseIterable {
    case flagEnabled
    case totalCount
    case timestamp
}

extension FoilDefaultStorage {
    init(wrappedValue: T, _ key: AppSettingsKey) {
        self.init(wrappedValue: wrappedValue, key: key.rawValue)
    }
}

extension FoilDefaultStorageOptional {
    init(_ key: AppSettingsKey) {
        self.init(key: key.rawValue)
    }
}

观察更改

许多方法来观察属性更改。最常见的方法是使用 Key-Value Observing 或 Combine Publisher。 KVO 观察要求具有该属性的对象继承自 NSObject,并且该属性必须声明为 @objc dynamic

final class AppSettings: NSObject {
    static let shared = AppSettings()

    @FoilDefaultStorageOptional(key: "userId")
    @objc dynamic var userId: String?

    @FoilDefaultStorageOptional(key: "average")
    var average: Double?
}

使用 KVO

let observer = AppSettings.shared.observe(\.userId, options: [.new]) { settings, change in
    print(change)
}

使用 Combine

注意

average 不需要 @objc dynamic 注解,.receiveValue 将立即触发,其中包含 average 的当前值,并在每次更改后触发。

AppSettings.shared.$average
    .sink {
        print($0)
    }
    .store(in: &cancellable)

Combine 使用 KVO 的替代方案

注意

在这种情况下,userId 需要 @objc dynamic 注解,并且 AppSettings 需要继承自 NSObject。 然后,receiveValue 将仅在对包装对象的 value 进行更改时触发。 它不会像上面的示例中那样发布初始值。

AppSettings.shared
    .publisher(for: \.userId, options: [.new])
    .sink {
        print($0)
    }
    .store(in: &cancellable)

支持的类型

默认情况下,以下类型支持与 @FoilDefaultStorage 一起使用。

注意

虽然 UserDefaultsSerializable 协议定义了一个*可能失败* 的初始化器,init?(storedValue:),但可以提供一个具有**非可能失败**初始化器的自定义实现,它仍然满足协议要求。

对于所有 Swift 的内置类型(BoolIntDoubleString 等),UserDefaultsSerializable 的默认实现是**非可能失败**。

重要提示

通过符合 UserDefaultsSerializable 可以添加对自定义类型的支持。 但是,**强烈建议不要这样做**,因为默认情况下支持所有 plist 类型。 UserDefaults 并非用于存储复杂的数据结构和对象图。 您可能应该使用适当的数据库(或通过 Codable 序列化到磁盘)。

虽然 Foil 默认支持存储 Codable 类型,但您应该**谨慎使用**,并且*仅*用于属性较少的小对象。

关于 Codable 类型的说明

警告

如果您正在存储自定义 Codable 类型并使用 Foil 提供的 UserDefaultsSerializable 的默认实现,那么**您必须使用属性包装器的可选变体** @FoilDefaultStorageOptional。 这将允许您对 Codable 类型进行重大更改(例如,添加或删除属性)。 或者,您可以提供支持迁移的 Codable 自定义实现,或提供处理编码/解码失败的 UserDefaultsSerializable 自定义实现。 请参见下面的示例。

Codable 示例

// Note: uses the default implementation of UserDefaultsSerializable
struct User: Codable, UserDefaultsSerializable {
    let id: UUID
    let name: String
}

// Yes, do this
@FoilDefaultStorageOptional(key: "user")
var user: User?

// NO, do NOT this
// This will crash if you change User by adding/removing properties
@FoilDefaultStorage(key: "user")
var user = User()

关于 RawRepresentable 类型的说明

使用 RawRepresentable 类型,尤其是作为 Codable 类型的属性时,需要特别注意。 如上所述,Codable 类型必须使用开箱即用的 @FoilDefaultStorageOptional,除非您提供 UserDefaultsSerializable 的自定义实现。 对于 RawRepresentable 类型,同样适用。

警告

如果修改 enum 的情况(或以其他方式使用重大更改修改 RawRepresentable),则 RawRepresentable 类型必须使用 @FoilDefaultStorageOptional。 此外,RawRepresentable 类型有一个指定的初始化器,该初始化器可能会失败,init?(rawValue:),因此可能会返回 nil

或者,如果要存储具有 RawRepresentable 属性的 Codable 类型,默认情况下,这些属性应为可选,以适应上述可选性。

如果您希望避免 RawRepresentable 类型的这些极端情况,则可以提供一个非可能失败的初始化器

extension MyStringEnum: UserDefaultsSerializable {
    // Default init provided by Foil
    // public init?(storedValue: RawValue.StoredValue) { ... }

    // New, non-failable init using force-unwrap.
    // Only do this if you know you will not make breaking changes.
    public init(storedValue: String) { self.init(rawValue: storedValue)! }
}

其他资源

支持的平台

要求

安装

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/jessesquires/Foil.git", from: "6.0.0")
]

或者,您可以直接通过 Xcode添加包。

文档

您可以在此处阅读文档。使用 jazzy 生成。由 GitHub Pages 托管。

文档也可在 Swift Package Index 上找到。

贡献

有兴趣为此项目做出贡献吗? 请查看以下指南。

也可以考虑赞助这个项目购买我的应用程序! ✌️

致谢

Jesse Squires 创建和维护。

许可证

在 MIT 许可证下发布。 有关详细信息,请参见 LICENSE

版权所有 © 2021-至今 Jesse Squires。