一个轻量级的 属性包装器,用于以正确的方式处理 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 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?
}
let observer = AppSettings.shared.observe(\.userId, options: [.new]) { settings, change in
print(change)
}
注意
average
不需要 @objc dynamic
注解,.receiveValue
将立即触发,其中包含 average
的当前值,并在每次更改后触发。
AppSettings.shared.$average
.sink {
print($0)
}
.store(in: &cancellable)
注意
在这种情况下,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 的内置类型(Bool
、Int
、Double
、String
等),UserDefaultsSerializable
的默认实现是**非可能失败**。
重要提示
通过符合 UserDefaultsSerializable
可以添加对自定义类型的支持。 但是,**强烈建议不要这样做**,因为默认情况下支持所有 plist
类型。 UserDefaults
并非用于存储复杂的数据结构和对象图。 您可能应该使用适当的数据库(或通过 Codable
序列化到磁盘)。
虽然 Foil
默认支持存储 Codable
类型,但您应该**谨慎使用**,并且*仅*用于属性较少的小对象。
Bool
Int
UInt
Float
Double
String
URL
Date
Data
Array
Set
Dictionary
RawRepresentable
类型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)! }
}
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。