我正试图想出一种使用属性访问UserDefaults的方法。但是,我陷入困境,因为我无法弄清楚如何使属性变得动态,可以这么说。
这是一般的想法,即我想要的API :
class AppUserDefaults {
var username = DefaultsKey<String>("username", defaultValue: "Unknown")
var age = DefaultsKey<Int?>("age", defaultValue: nil)
}
let props = AppUserDefaults()
props.username = "bla"
print("username: \(props.username)")
但是(当然)不起作用,因为类型是DefaultsKey<String>
,而不是String
。所以我必须为DefaultsKey
实现添加一个value属性,只是为了添加getter和setter,如下所示:
struct DefaultsKey<ValueType> {
private let key: String
private let defaultValue: ValueType
public init(_ key: String, defaultValue: ValueType) {
self.key = key
self.defaultValue = defaultValue
}
var value: ValueType {
get {
let value = UserDefaults.standard.object(forKey: key)
return value as? ValueType ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
}
然后像这样使用它:
let props = AppUserDefaults()
props.username.value = "bla"
print("username: \(props.username.value)")
但我觉得这很难看。我还尝试了下标方法,但是您仍然需要添加[]
而不是.value
:
struct DefaultsKey<ValueType> {
...
subscript() -> ValueType {
get {
let value = UserDefaults.standard.object(forKey: key)
return value as? ValueType ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
}
let props = AppUserDefaults()
props.username[] = "bla"
print("username: \(props.username[])")
基本上我想要的是我可以直接在DefaultsKey
上定义getter和setter,而不必通过value
属性。这对Swift来说根本不可能吗?是否有其他方法可以获得我想要的行为,AppUserDefaults
上定义的属性是&#34;动态&#34;并通过getter和setter,而不必在AppUserDefaults
内的属性声明中定义它?
我希望我在这里使用正确的术语,并为每个人提出明确的问题。
答案 0 :(得分:4)
我唯一能想到的是:
Comparator<T> c = new Comparator<T>() {
@Override
public int compare(T o1, T o2) {
return 0;
}
};
答案 1 :(得分:2)
除了所有提议的变体外,您还可以定义自定义运算符,以便为DefaultsKey
结构赋值。
为此,您的DefaultsKey
结构应如下所示:
struct DefaultsKey<ValueType> {
private let key: String
private let defaultValue: ValueType
public init(_ key: String, defaultValue: ValueType) {
self.key = key
self.defaultValue = defaultValue
}
public private(set) var value: ValueType {
get {
let value = UserDefaults.standard.object(forKey: key)
return value as? ValueType ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
}
}
}
DefaultsKey
代码块的说明:
private(set) var
表示您只能在可以使用private
访问级别访问此属性的位置设置此属性的值(您也可以写internal(set)
或fileprivate(set)
能够相应地从internal
和fileprivate
访问级别设置它。您需要稍后设置
value
属性。要访问此值,getter定义为public
(在public
之前写private(set)
)。- 醇>
您不需要使用
synchronize()
方法(“此方法是不必要的,不应使用”,参考: https://developer.apple.com/documentation/foundation/userdefaults/1414005-synchronize)。
现在是时候定义自定义操作符了(你可以根据需要命名,这只是举例):
infix operator <<<
extension DefaultsKey {
static func <<<(left: inout DefaultsKey<ValueType>, right: ValueType) {
left.value = right
}
}
使用此运算符,您无法设置错误类型的值,因此它是类型安全的。
要测试你可以使用一点修改你的代码:
class AppUserDefaults {
var username = DefaultsKey<String>("username", defaultValue: "Unknown")
var age = DefaultsKey<Int?>("age", defaultValue: nil)
}
let props = AppUserDefaults()
props.username <<< "bla"
props.age <<< 21
props.username <<< "Yo man"
print("username: \(props.username.value)")
print("username: \(props.age.value)")
希望它有所帮助。
答案 2 :(得分:0)
radex.io中有一个really good blog post关于静态类型的NSUserDefaults,如果我理解你要做的事情,你可能会发现它很有用。
我已经为最新的Swift更新了它(因为我们现在有了条件一致性),并为我的实现添加了一个默认值,我已在下面列出。
class DefaultsKeys {}
class DefaultsKey<T>: DefaultsKeys {
let key: String
let defaultResult: T
init (_ key: String, default defaultResult: T) {
self.key = key
self.defaultResult = defaultResult
}
}
extension UserDefaults {
subscript<T>(key: DefaultsKey<T>) -> T {
get {
return object(forKey: key.key) as? T ?? key.defaultResult
}
set {
set(newValue, forKey: key.key)
}
}
}
// usage - setting up the keys
extension DefaultsKeys {
static let baseCurrencyKey = DefaultsKey<String>("Base Currency", default: Locale.current.currencyCode!)
static let archiveFrequencyKey = DefaultsKey<Int>("Archive Frequency", default: 30)
static let usePasswordKey = DefaultsKey<Bool>("Use Password", default: false)
}
// usage - getting and setting a value
let uDefaults = UserDefaults.standard
uDefaults[.baseCurrencyKey] = "GBP"
let currency = uDefaults[.baseCurrencyKey]
答案 3 :(得分:0)
以下是我们在当前项目中的表现。它类似于其他一些答案,但我认为更少的锅炉板。以isFirstLaunch为例。
enum UserDafaultsKeys: String {
case isFirstLaunch
}
extension UserDefaults {
var isFirstLaunch: Bool {
set {
set(!newValue, forKey: UserDafaultsKeys.isFirstLaunch.rawValue)
synchronize()
}
get { return !bool(forKey: UserDafaultsKeys.isFirstLaunch.rawValue) }
}
}
然后像这样使用。
UserDefaults.standard.isFirstLaunch = true
它具有不需要学习单独的对象的好处,并且非常清楚存储变量的位置。