Swift. UserDefaults. Property wrapper

—

от автора

Xcode
Xcode

Вряд ли можно представить хоть одно приложение, которое не требует сохранения настроек пользователя. Очевидно, что для этих целей существует масса решений, и каждое из них решает одну, конкретную задачу. Сегодня речь пойдет о постоянном хранилище данных UserDefaults и его использовании для хранения данных.

Реализация

Выполнив поиск существующих решений на просторах сети интернет были найдены следующие материалы:

  1. Property wrappers in Swift

  2. Create the Perfect UserDefaults Wrapper Using Property Wrapper

  3. A better approach to writing a UserDefaults Property Wrapper

Однако все они имеют несколько недостатков:

  1. Значение не может быть опциональным, вследствие чего требуется указывать значение по-умолчанию

  2. Значение не может быть перечислением или OptionSet

  3. Все они позволяют сохранить в хранилище неподдерживаемые типы данных, вследствие чего приложение аварийно завершает свою работу

Но если обратиться к первоисточнику и изучить AppStorage от Apple, то единственный недостаток, присущий уже к данному решению является минимальная версия iOS 14, а также зависимость от framework SwiftUI.

Ниже предлагается propertyWrapper UserDefault, который лишен всех перечисленных недостатков, в то время как решает поставленную задачу.

import SwiftUI  @propertyWrapper public struct UserDefault<Value>: DynamicProperty { private let get: () -> Value private let set: (Value) -> Void  public var wrappedValue: Value { get { get() } nonmutating set { set(newValue) } } }  public extension UserDefault { init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Bool { self.init(defaultValue: wrappedValue, key: key, store: store) }  init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Int { self.init(defaultValue: wrappedValue, key: key, store: store) }  init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Double { self.init(defaultValue: wrappedValue, key: key, store: store) }  init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == String { self.init(defaultValue: wrappedValue, key: key, store: store) }  init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == URL { self.init(defaultValue: wrappedValue, key: key, store: store) }  init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Data { self.init(defaultValue: wrappedValue, key: key, store: store) }  private init(defaultValue: Value, key: String, store: UserDefaults) { get = { let value = store.value(forKey: key) as? Value return value ?? defaultValue }  set = { newValue in store.set(newValue, forKey: key) } } }  public extension UserDefault where Value: ExpressibleByNilLiteral { init(_ key: String, store: UserDefaults = .standard) where Value == Bool? { self.init(wrappedType: Bool.self, key: key, store: store) }  init(_ key: String, store: UserDefaults = .standard) where Value == Int? { self.init(wrappedType: Int.self, key: key, store: store) }  init(_ key: String, store: UserDefaults = .standard) where Value == Double? { self.init(wrappedType: Double.self, key: key, store: store) }  init(_ key: String, store: UserDefaults = .standard) where Value == String? { self.init(wrappedType: String.self, key: key, store: store) }  init(_ key: String, store: UserDefaults = .standard) where Value == URL? { self.init(wrappedType: URL.self, key: key, store: store) }  init(_ key: String, store: UserDefaults = .standard) where Value == Data? { self.init(wrappedType: Data.self, key: key, store: store) }  private init<T>(wrappedType: T.Type, key: String, store: UserDefaults) { get = { let value = store.value(forKey: key) as? Value return value ?? nil }  set = { newValue in let newValue = newValue as? Optional<T>  if let newValue { store.set(newValue, forKey: key) } else { store.removeObject(forKey: key) } } } }  public extension UserDefault where Value: RawRepresentable { init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value.RawValue == String { self.init(defaultValue: wrappedValue, key: key, store: store) }  init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value.RawValue == Int { self.init(defaultValue: wrappedValue, key: key, store: store) }  private init(defaultValue: Value, key: String, store: UserDefaults) { get = { var value: Value?  if let rawValue = store.value(forKey: key) as? Value.RawValue { value = Value(rawValue: rawValue) }  return value ?? defaultValue }  set = { newValue in let value = newValue.rawValue store.set(value, forKey: key) } } }  public extension UserDefault { init<R>(_ key: String, store: UserDefaults = .standard) where Value == R?, R: RawRepresentable, R.RawValue == Int { self.init(key: key, store: store) }  init<R>(_ key: String, store: UserDefaults = .standard) where Value == R?, R: RawRepresentable, R.RawValue == String { self.init(key: key, store: store) }  private init<R>(key: String, store: UserDefaults) where Value == R?, R: RawRepresentable { get = { if let rawValue = store.value(forKey: key) as? R.RawValue { return R(rawValue: rawValue) } else { return nil } }   set = { newValue in let newValue = newValue as Optional<R>  if let newValue { store.set(newValue.rawValue, forKey: key) } else { store.removeObject(forKey: key) } } } } 

Стоит отметить, что использование propertyWrapper намного удобней, так как кода становится существенно меньше, соответственно и читается он легче.

final class UserSettings { @UserDefault("age") var age: Int?  @UserDefault("name") var name: String?  @UserDefault("planet") var planet: Planet = .earth  func clear() { age = nil name = nil planet = .earth } }

Заключение

В последнее время, с момента появления SwiftUI, все большей популярностью пользуются @propertyWrapper. Однако использование таких объектов накладывает дополнительные ограничения. К примеру, необходимо поднимать минимальную версию iOS и внедрять дополнительную зависимость от framework SwiftUI. Но это вовсе не означает, что нужно пользоваться тем, что есть, и пример тому был описан в этой статье.

  1. Оригинал статьи

  2. UserDefaults

  3. AppStorage


ссылка на оригинал статьи https://habr.com/ru/articles/670378/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *