KVO keyPathsForValuesAffecting不起作用

时间:2018-06-29 11:44:05

标签: ios swift key-value-observing

我对keyPathsForValuesAffecting<key>方法有疑问。当fullNamename更改时,我想通知观察者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 :(得分:5)

您需要在@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)]
}

您还应该按照Ken Thomases的建议在上游属性(dynamicname)上使用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

答案 1 :(得分:0)

这应该可以解决您的问题。

 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保留在内存中。

答案 2 :(得分:0)

namesurname属性也都必须为@objc dynamic。由于使用了keyPathsForValuesAffectingFullName方法,因此KVO会在观察到fullName时观察到它们。 @objc是允许被观察到的必要条件。 dynamic对于使Swift每次设置时实际上调用setter(KVO所钩挂的东西)是必要的,而不是内联它而只是设置背景实例变量。