Как разработать модель настроек для общего получения + заданных значений для всех ключей UserDefaults

Я настраиваю свой класс настроек, который получает / устанавливает значения из UserDefaults. Я хочу, чтобы как можно больше кода был универсальным, чтобы свести к минимуму усилия, требуемые всякий раз, когда вводится новый параметр (у меня их уже много, и я ожидаю, что в будущем будет гораздо больше), тем самым уменьшая вероятность любых человеческих ошибок / ошибок .

Я наткнулся на этот ответ, чтобы создать оболочку для 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"
}

В этом упрощенном примере класса Settings вы можете видеть, что при его создании я инициализирую значение каждого определенного параметра. Я проверяю, был ли найден его ключ настройки в 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>(), при котором я передаю ему ключ для настройки (и ничего более), и он обрабатывает все остальное?

Я определил каждую настройку, чтобы иметь следующие общие элементы:

  1. ключ настроек (например, SettingsKey.TimeFormat в моем примере)
  2. уникальный тип (может быть AnyObject, String, Int, Bool и т. д., например, enum TimeFormat в моем примере)
  3. уникальное свойство (например, timeFormat в моем примере)
  4. значение по умолчанию (например, 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
}

To:

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() по-прежнему необходима, когда ее значение изменяется пользователем в приложении.


person Barrrdi    schedule 02.01.2021    source источник


Ответы (1)


Обратите внимание, что ваши настройки не обязательно должны сохранять свойства. Их можно вычислить свойствами:

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
}

Теперь, чтобы добавить новый параметр, вам просто нужно написать новый регистр перечисления для ключа и еще две строки кода.

person Sweeper    schedule 02.01.2021