За годы работы разработчиком iOS, я собрал множество инструментов и полезных штук, которые облегчают процесс разработки. В этой статье, я хочу поделиться одним из таких инструментов. Это будет не большая статья. Я покажу, как пользоваться этой утилитой, продемонстрирую её в действии. Надеюсь, что статья окажется полезной для вас.

Работа с UserDefaults может быть удобной и простой, в этом поможет обертка UserDefaultsWrapper. Давайте разберемся, как она работает.

UserDefaultsWrapper – это свойство-обертка (property wrapper), которое облегчает сохранение и извлечение данных из UserDefaults. Он особенно полезен, когда вы работаете с типами данных, такими как URL, массивы, словари, примитивные типы (String, Int, Bool и т.д.).

import Foundation

/// - Parameters:
///   - key: Data will be saved and extracted using this key
///   - defaultValue: This value will be substituted when there is no value
///   - userDefaults: NSUserDefaults is a hierarchical persistent interprocess (optionally distributed) key-value store, optimized for storing user settings.
///
/// Types an UserDefaultsWrapper can work with:
///```
///  URL?
///  [Any]?
///  [String : Any]?
///  String?
///  [String]?
///  Data?
///  Bool
///  Int
///  Float
///  Double
/// ```
///
/// - Warning:
///  If the type is not optional, default == nil will cause a crash.
///
/// Exemple:
/// ```
/// class User {
///
///    @UserDefaultsWrapper<String>(key: "FirstName", default: "John") var firstName
///    @UserDefaultsWrapper(key: "LastName", default: "John") var lastName: String
///
///    // OR
///
///    @UserDefaultsWrapper(key: "Age") var age: Int?
///    @UserDefaultsWrapper(key: "Old", default: 6) var old: Int?
/// }
///
/// let user = User()
/// print(user.firstName) // John
/// user.firstName = "Misha"
/// print(user.firstName) // Misha
/// $user.firstName.removeObject()
///
/// print(user.old) // optional(6)
/// user.old = 18
/// print(user.old) // optional(18)
/// user.old = nil
/// print(user.old) // optional(6)
///
/// print(user.age) // nil
/// user.age = 18
/// print(user.age) // optional(18)
/// user.age = nil
/// print(user.age) // nil
/// ```
@propertyWrapper
public struct UserDefaultsWrapper<T> {
    private let key: String
    private let defaultValue: T!
    private let userDefaults: UserDefaults

    public var wrappedValue: T {
        get {
            let anyValue = userDefaults.value(forKey: key)
            let value: T = (anyValue as? T) ?? defaultValue
            return value
        }
        set {
            if let optional = newValue as? AnyOptional, optional.isNil {
                userDefaults.removeObject(forKey: key)

                if let defaultValue = defaultValue {
                    self.set(newValue: defaultValue)
                }
            } else {
                self.set(newValue: newValue)
            }
            userDefaults.synchronize()
        }
    }

    public var projectedValue: ActionUserDefault { self }

    /// - Parameters:
    ///   - key: Data will be saved and extracted using this key
    ///   - defaultValue: This value will be substituted when there is no value
    ///   - userDefaults: NSUserDefaults is a hierarchical persistent interprocess (optionally distributed) key-value store, optimized for storing user settings.
    ///
    /// - Warning:
    ///  If the type is not optional, default == nil will cause a crash.
    public init(key: String, default defaultValue: T? = nil, userDefaults: UserDefaults = .standard) {
        self.key = key
        self.userDefaults = userDefaults
        self.defaultValue = defaultValue
    }

    private func set(newValue: T) {
        userDefaults.setValue(newValue, forKey: key)
    }
}

// MARK: - ActionUserDefault

extension UserDefaultsWrapper: ActionUserDefault {

    public func removeObject() {
        userDefaults.removeObject(forKey: key)

        if let defaultValue = defaultValue {
            self.set(newValue: defaultValue)
        }
    }
}

// MARK: - Helper classes

private protocol AnyOptional {
    var isNil: Bool { get }
}

extension Optional: AnyOptional {
    var isNil: Bool { self == nil }
}

public protocol ActionUserDefault {
    func removeObject()
}

Использование

Пример использования UserDefaultsWrapper может выглядеть так:

class User {
  
    @UserDefaultsWrapper<String>(key: "FirstName", default: "John") var firstName
    @UserDefaultsWrapper(key: "LastName", default: "John") var lastName: String
 
    @UserDefaultsWrapper(key: "Age") var age: Int?
    @UserDefaultsWrapper(key: "Old", default: 6) var old: Int?
}

Здесь firstName, lastName , Age и Old – это свойства, которые автоматически сохраняют свои значения в UserDefaults и извлекают их оттуда.

UserDefaultsWrapper также включает в себя метод removeObject(), который удаляет значение из UserDefaults и, при наличии, возвращает значение по умолчанию.

$user.firstName.removeObject()

Преимущества и Применение

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

Еще статьи Swift Utilities:

Комментарии (0)