Swift 4: getter и setter непосредственно в структуре или классе
Я пытаюсь найти способ доступа к 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
. Поэтому я должен добавить свойство value в реализацию DefaultsKey
чтобы добавить 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[])")
В принципе, я хочу, чтобы я мог определить getter и setter непосредственно на DefaultsKey
вместо того, чтобы пройти через это свойство value
. Это просто невозможно с Swift? Есть ли другой способ получить поведение, которое я хочу, где свойства, определенные в AppUserDefaults
являются "динамическими" и проходят через getter и setter, без необходимости определять его в объявлении свойства внутри AppUserDefaults
?
Надеюсь, я использую здесь правильные условия и поставил вопрос каждому.
Ответы
Ответ 1
Единственное, о чем я могу думать, это:
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)")
Ответ 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)")
Надеюсь, поможет.
Ответ 3
Существует очень хорошая запись в блоге из radex.io о статически типизированных 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]
Ответ 4
Вот как мы это делаем в моем текущем проекте. Это похоже на некоторые другие ответы, но я думаю, что еще меньше плита котла. В качестве примера можно использовать 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
Он имеет то преимущество, что вам не нужно изучать отдельный объект для использования и очень ясно, где хранится переменная.