如何设计一个设置模型来为所有 UserDefaults 键一般获取+设置值
How to design a Settings model to generically get + set values for all UserDefaults keys
我正在设置我的设置 class,其中 gets/sets 值来自 UserDefaults。我希望尽可能多的代码是通用的,以尽量减少引入新设置时所涉及的工作量(我现在有很多,我希望将来会有更多),从而减少任何人的可能性 errors/bugs.
我发现 为 UserDefaults 创建包装器:
struct UserDefaultsManager {
static var userDefaults: UserDefaults = .standard
static func set<T>(_ value: T, forKey: String) where T: Encodable {
if let encoded = try? JSONEncoder().encode(value) {
userDefaults.set(encoded, forKey: forKey)
}
}
static func get<T>(forKey: String) -> T? where T: Decodable {
guard let data = userDefaults.value(forKey: forKey) as? Data,
let decodedData = try? JSONDecoder().decode(T.self, from: data)
else { return nil }
return decodedData
}
}
我创建了一个枚举来存储所有设置键:
enum SettingKeys: String {
case TimeFormat = "TimeFormat"
// and many more
}
每个设置都有自己的枚举:
enum TimeFormat: String, Codable {
case ampm = "12"
case mili = "24"
}
在这个简化的设置 class 示例中,您可以看到在实例化时我初始化了每个已定义设置的值。我检查它的设置键是否在 UserDefaults 中找到:如果是,我使用找到的值,否则我将其设置为默认值并首次将其保存到 UserDefaults.
class Settings {
var timeFormat: TimeFormat!
init() {
self.initTimeFormat()
}
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
self.setTimeFormat(to: .ampm)
return
}
self.timeFormat = format
}
func setTimeFormat(to format: TimeFormat) {
UserDefaultsManager.set(format, forKey: SettingKeys.TimeFormat.rawValue)
self.timeFormat = format
}
}
这工作正常,而且非常简单。然而,提前考虑,为这个应用程序中的每个设置(以及我希望在未来做的所有其他设置)复制这将是乏味的(因此容易出错)。有没有一种方法可以使 init<name of setting>()
和 set<name of setting>()
通用化,从而我将它传递给一个设置键(仅此而已),它会处理其他所有事情?
我已经确定每个设置都有这些共享元素:
- 设置键(例如,在我的示例中为 SettingsKey.TimeFormat)
- 唯一类型(可以是 AnyObject、String、Int、Bool 等。例如我的示例中的枚举 TimeFormat)
- 唯一 属性(例如我的示例中的 timeFormat)
- 默认值(例如在我的示例中为 TimeFormat.ampm)
一如既往的感谢!
更新:
这可能会或可能不会有所不同,但考虑到所有设置都有默认值,我意识到它们不需要是非可选的可选但可以在初始化时设置默认值。
即变化:
var timeFormat: TimeFormat!
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
self.setTimeFormat(to: .ampm)
return
}
self.timeFormat = format
}
收件人:
var timeFormat: TimeFormat = .ampm
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
UserDefaultsManager.set(self.timeFormat, forKey: SettingKeys.TimeFormat.rawValue)
return
}
self.timeFormat = format
}
当用户在应用程序内更改其值时,仍然需要 setTimeFormat()
函数。
请注意,您的设置不必是存储属性。它们可以计算属性:
var timeFormat: TimeFormat {
get {
UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) ?? .ampm
}
set {
UserDefaultsManager.set(newValue, forKey: SettingKeys.TimeFormat.rawValue)
}
}
当您想添加新设置时,您不必用这种方式编写那么多代码。因为您将只添加一个新的设置键枚举大小写和计算的 属性.
此外,这个计算的 属性 可以包装到 属性 包装器中:
@propertyWrapper
struct FromUserDefaults<T: Codable> {
let key: SettingKeys
let defaultValue: T
init(key: SettingKeys, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
UserDefaultsManager.get(forKey: key.rawValue) ?? defaultValue
}
set {
UserDefaultsManager.set(newValue, forKey: key.rawValue)
}
}
}
用法:
class Settings {
@FromUserDefaults(key: .TimeFormat, defaultValue: .ampm)
var timeFormat: TimeFormat
}
现在要添加一个新的设置,你只需要为key写一个新的enum case,再写两行代码。
我正在设置我的设置 class,其中 gets/sets 值来自 UserDefaults。我希望尽可能多的代码是通用的,以尽量减少引入新设置时所涉及的工作量(我现在有很多,我希望将来会有更多),从而减少任何人的可能性 errors/bugs.
我发现
struct UserDefaultsManager {
static var userDefaults: UserDefaults = .standard
static func set<T>(_ value: T, forKey: String) where T: Encodable {
if let encoded = try? JSONEncoder().encode(value) {
userDefaults.set(encoded, forKey: forKey)
}
}
static func get<T>(forKey: String) -> T? where T: Decodable {
guard let data = userDefaults.value(forKey: forKey) as? Data,
let decodedData = try? JSONDecoder().decode(T.self, from: data)
else { return nil }
return decodedData
}
}
我创建了一个枚举来存储所有设置键:
enum SettingKeys: String {
case TimeFormat = "TimeFormat"
// and many more
}
每个设置都有自己的枚举:
enum TimeFormat: String, Codable {
case ampm = "12"
case mili = "24"
}
在这个简化的设置 class 示例中,您可以看到在实例化时我初始化了每个已定义设置的值。我检查它的设置键是否在 UserDefaults 中找到:如果是,我使用找到的值,否则我将其设置为默认值并首次将其保存到 UserDefaults.
class Settings {
var timeFormat: TimeFormat!
init() {
self.initTimeFormat()
}
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
self.setTimeFormat(to: .ampm)
return
}
self.timeFormat = format
}
func setTimeFormat(to format: TimeFormat) {
UserDefaultsManager.set(format, forKey: SettingKeys.TimeFormat.rawValue)
self.timeFormat = format
}
}
这工作正常,而且非常简单。然而,提前考虑,为这个应用程序中的每个设置(以及我希望在未来做的所有其他设置)复制这将是乏味的(因此容易出错)。有没有一种方法可以使 init<name of setting>()
和 set<name of setting>()
通用化,从而我将它传递给一个设置键(仅此而已),它会处理其他所有事情?
我已经确定每个设置都有这些共享元素:
- 设置键(例如,在我的示例中为 SettingsKey.TimeFormat)
- 唯一类型(可以是 AnyObject、String、Int、Bool 等。例如我的示例中的枚举 TimeFormat)
- 唯一 属性(例如我的示例中的 timeFormat)
- 默认值(例如在我的示例中为 TimeFormat.ampm)
一如既往的感谢!
更新:
这可能会或可能不会有所不同,但考虑到所有设置都有默认值,我意识到它们不需要是非可选的可选但可以在初始化时设置默认值。
即变化:
var timeFormat: TimeFormat!
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
self.setTimeFormat(to: .ampm)
return
}
self.timeFormat = format
}
收件人:
var timeFormat: TimeFormat = .ampm
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
UserDefaultsManager.set(self.timeFormat, forKey: SettingKeys.TimeFormat.rawValue)
return
}
self.timeFormat = format
}
当用户在应用程序内更改其值时,仍然需要 setTimeFormat()
函数。
请注意,您的设置不必是存储属性。它们可以计算属性:
var timeFormat: TimeFormat {
get {
UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) ?? .ampm
}
set {
UserDefaultsManager.set(newValue, forKey: SettingKeys.TimeFormat.rawValue)
}
}
当您想添加新设置时,您不必用这种方式编写那么多代码。因为您将只添加一个新的设置键枚举大小写和计算的 属性.
此外,这个计算的 属性 可以包装到 属性 包装器中:
@propertyWrapper
struct FromUserDefaults<T: Codable> {
let key: SettingKeys
let defaultValue: T
init(key: SettingKeys, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
UserDefaultsManager.get(forKey: key.rawValue) ?? defaultValue
}
set {
UserDefaultsManager.set(newValue, forKey: key.rawValue)
}
}
}
用法:
class Settings {
@FromUserDefaults(key: .TimeFormat, defaultValue: .ampm)
var timeFormat: TimeFormat
}
现在要添加一个新的设置,你只需要为key写一个新的enum case,再写两行代码。