method - Swift 4: getter y setter directamente en una estructura o clase
swift set getter and setter (4)
Además de todas las variantes propuestas, también puede definir su operador personalizado para asignar valor a la estructura DefaultsKey
.
Para eso, su estructura DefaultsKey
debe verse así:
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)
}
}
}
Explicación para el bloque de código
DefaultsKey
:
private(set) var
significa que puede establecer el valor de esta propiedad solo donde puede acceder a ella conprivate
nivel de accesoprivate
(también puede escribirinternal(set)
ofileprivate(set)
para poder establecerla desde niveles de accesointernal
y defileprivate
en consecuencia).Tendrá que establecer la propiedad de
value
más adelante. Para acceder a este valor, el captador se define comopublic
(escribiendopublic
antes deprivate(set)
).No es necesario que utilice el método
synchronize()
("este método no es necesario y no debe usarse", consulte: https://developer.apple.com/documentation/foundation/userdefaults/1414005-synchronize ).
Ahora es el momento de definir un operador personalizado (puedes nombrarlo como quieras, esto es solo por ejemplo):
infix operator <<<
extension DefaultsKey {
static func <<<(left: inout DefaultsKey<ValueType>, right: ValueType) {
left.value = right
}
}
Con este operador no podría establecer un valor de tipo incorrecto, por lo que es seguro para el tipo.
Para probar puedes usar un poco modificado tu código:
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)")
Espero eso ayude.
Estoy tratando de encontrar una manera de acceder a UserDefaults usando propiedades. Sin embargo, estoy atascado porque no puedo entender cómo hacer que las propiedades sean dinámicas, por así decirlo.
Esta es la idea genérica, esa API que quiero :
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)")
Pero eso (por supuesto) no funciona, ya que el tipo es DefaultsKey<String>
, no String
. Así que tengo que agregar una propiedad de valor a la implementación de DefaultsKey
solo para agregar el getter y el setter, de esta manera:
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()
}
}
}
y luego usarlo así:
let props = AppUserDefaults()
props.username.value = "bla"
print("username: /(props.username.value)")
Pero eso me parece bastante feo. También probé un método de subíndice, pero aún así debes agregar []
lugar de .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[])")
Básicamente, lo que quiero es que pueda definir el getter y el setter directamente en DefaultsKey
lugar de tener que pasar por esa propiedad de value
. ¿Esto simplemente no es posible con Swift? ¿Hay otra forma de obtener el comportamiento que deseo, donde las propiedades definidas en AppUserDefaults
son "dinámicas" y pasan por un getter y setter, sin tener que definirlo en la declaración de propiedad dentro de AppUserDefaults
?
Espero estar usando los términos correctos aquí y aclarar la pregunta para todos.
Aquí es cómo lo estamos haciendo en mi proyecto actual. Es similar a algunas otras respuestas, pero creo que incluso menos placa de la caldera. Usando isFirstLaunch como ejemplo.
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) }
}
}
Entonces se usa así.
UserDefaults.standard.isFirstLaunch = true
Tiene los beneficios de no necesitar aprender un objeto separado para usar y es muy claro dónde se almacena la variable.
Hay una muy buena publicación en el blog de radex.io sobre NSUserDefaults de tipo estático que podría resultarle útil si entiendo lo que está intentando hacer.
Lo actualicé para el último Swift (ya que ahora tenemos conformidad condicional) y agregué un valor predeterminado para mi implementación, que he establecido a continuación.
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]
Lo único que puedo pensar es esto:
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()
}
}
}
class AppUserDefaults {
private var _username = DefaultsKey<String>("username", defaultValue: "Unknown")
var username: String {
set {
_username.value = newValue
},
get {
return _username.value
}
}
}
let props = AppUserDefaults()
props.username = "bla"
print("username: /(props.username)")