KVO keyPathsForValuesAffecting не работает - PullRequest
0 голосов
/ 29 июня 2018

У меня проблема с методом keyPathsForValuesAffecting<key>. Я хотел бы уведомить наблюдателей о fullName, когда name или surname изменились. Но, к сожалению, наблюдатели не уведомлены.

Мой код:

класс, который будет наблюдаться:

class DependencyTest: NSObject {
    @objc dynamic var fullName: String {
        return name + " " + surname
    }
    @objc var name = ""
    @objc var surname = ""

    class func keyPathsForValuesAffectingFullName() -> Set<NSObject> {
        return ["name" as NSObject, "surname" as NSObject]
    }
}

наблюдатель ViewController:

let dep = DependencyTest()

    override func viewDidLoad() {
        super.viewDidLoad()

        addObserver(self, forKeyPath: "dep.fullName", options: .prior, context: nil)

        dep.name = "bob" // Im expecting that `observeValue:` method will be fired
        dep.surname = "gril"

    }

    override func observeValue(forKeyPath keyPath: String?,
                               of object: Any?,
                               change: [NSKeyValueChangeKey : Any]?,
                               context: UnsafeMutableRawPointer?) {
        print("?" + keyPath!) // not called
    }

Спасибо!

Ответы [ 3 ]

0 голосов
/ 29 июня 2018

Свойства name и surname тоже должны быть @objc dynamic. Из-за вашего keyPathsForValuesAffectingFullName метода KVO наблюдает их, когда наблюдается fullName. @objc необходимо, чтобы их можно было наблюдать. dynamic необходимо для того, чтобы Swift действительно вызывал сеттер (к чему подключается KVO) каждый раз, когда они установлены, вместо того, чтобы вставлять его и просто устанавливать переменную резервного копирования.

0 голосов
/ 29 июня 2018

Вам необходимо использовать @objc в вашем методе keyPathsForValuesAffecting, чтобы механизм KVO мог найти его с помощью среды выполнения Objective C:

@objc class func keyPathsForValuesAffectingFullName() -> Set<NSObject> {
    return ["name, "surname"]
}

Кстати, вместо этого вы можете использовать свойство и специальную форму #keyPath, чтобы компилятор помог вам обнаруживать ошибки:

@objc class var keyPathsForValuesAffectingFullName: Set<String> {
    return [#keyPath(name), #keyPath(surname)]
}

Вы также должны использовать dynamic в свойствах восходящего потока (name и surname), как советовал Кен Томасес.

Вот полная программа тестирования (как программа командной строки macOS):

import Foundation

class DependencyTest: NSObject {
    @objc dynamic var fullName: String {
        return name + " " + surname
    }
    @objc dynamic var name = ""
    @objc dynamic var surname = ""

    @objc class var keyPathsForValuesAffectingFullName: Set<String> {
        return [#keyPath(name), #keyPath(surname)]
    }
}

class Observer: NSObject {
    @objc let dep: DependencyTest

    init(dep: DependencyTest) {
        self.dep = dep
        super.init()
        addObserver(self, forKeyPath: #keyPath(Observer.dep.fullName), options: .prior, context: nil)
    }

    deinit {
        removeObserver(self, forKeyPath: #keyPath(Observer.dep.fullName), context: nil)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print("kvo: \(keyPath!) \(change?[.notificationIsPriorKey] as? Bool ?? false ? "prior" : "post")")
    }
}

let d = DependencyTest()
let o = Observer(dep: d)
d.name = "Robert"

Выход:

kvo: dep.fullName prior
kvo: dep.fullName post
Program ended with exit code: 0
0 голосов
/ 29 июня 2018

Это должно решить вашу проблему.

 class ViewController: UIViewController {

    @objc let dep = DependencyTest()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.addObserver(self, forKeyPath: #keyPath(dep.fullName), options: .new, context: nil)
        self.dep.name = "bob" // Im expecting that `observeValue:` method will be fired
        self.dep.surname = "gril"
    }

    override func viewWillDisappear(_ animated: Bool) {
        self.removeObserver(self, forKeyPath: #keyPath(dep.fullName))
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print("?" + keyPath!) // not called
        print("Full Name = ", dep.fullName)
    }
}

 class DependencyTest: NSObject {
    @objc dynamic var fullName: String = ""
    var name = "" {
        didSet {
            fullName = name + " " + surname
        }
    }
    var surname = "" {
        didSet {
            fullName = name + " " + surname
        }
    }
}

Переменная, запускаемая как функция, не будет наблюдаемой, вам нужно установить фактическое значение. Также не забудьте удалить наблюдателей, поскольку это не встроенная функция swift, она не деинициализируется и может удерживать ваш VC в памяти после того, как вы его отклоните.

...