Реализация UserDefaults с использованием ownyWrapper вызывает странную проблему - PullRequest
1 голос
/ 19 июня 2020

Я пытаюсь реализовать класс UserDefaults, используя @propertyWrapper. Я пытаюсь создать класс-оболочку для предпочтений пользователя моего приложения. Итак, я написал следующий код.

@propertyWrapper
struct Storage<T> {
    private let key: String
    private let defaultValue: T
    var projectedValue: Storage<T> { return self }
    var wrappedValue: T {
        get {
            return UserDefaults.standard.string(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }

    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    func observe(change: @escaping (T?, T?) -> Void) -> NSObject {
        return DefaultsObservation(key: key) { old, new in
            change(old as? T, new as? T)
        }
    }
}

Затем я хотел бы наблюдать за изменениями значений UserDefaults. Итак, я реализовал класс наблюдения с именем DefaultsObservation.

class DefaultsObservation: NSObject {
    let key: String
    private var onChange: (Any, Any) -> Void

    init(key: String, onChange: @escaping (Any, Any) -> Void) {
        self.onChange = onChange
        self.key = key
        super.init()
        UserDefaults.standard.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        guard let change = change, object != nil, keyPath == key else { return }
        onChange(change[.oldKey] as Any, change[.newKey] as Any)
    }

    deinit {
        UserDefaults.standard.removeObserver(self, forKeyPath: key, context: nil)
    }
}

Также следует мой класс AppData.

struct AppData {
    @Storage(key: "layout_key", defaultValue: "list")
    static var layout: String
}

Однако, когда я пытался добавить и прослушать изменения, свойство layout он не работает должным образом.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    AppData.$layout.observe { old, new in
        print(old)
    }
}

Когда я его отлаживал, deinit работает, как только вызывается метод viewWillAppear. Когда я закомментировал deinit метод удаления наблюдателя, все работает отлично. Думаю, закрытие deinit может вызвать проблемы с памятью. Так что я не хочу это комментировать. Что мне не хватает и как это решить?

1 Ответ

0 голосов
/ 19 июня 2020

Метод observe(change: @escaping (T?, T?) инициализирует DefaultsObservation объект и возвращает значение. На этот объект нет сильной ссылки, поэтому он освобожден и вызывается deInit. Вам нужно сохранить сильную ссылку на этот объект. Например,

var valueObserver: NSObject? = nil
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    valueObserver = AppData.$layout.observe { old, new in
        print(old)
    }
}
...