【问题标题】:KVO keyPathsForValuesAffecting doesn't workKVO keyPathsForValuesAffecting 不起作用
【发布时间】:2018-06-29 11:44:05
【问题描述】:

keyPathsForValuesAffecting<key> 方法有问题。当namesurname 发生变化时,我想通知fullName 的观察者。但不幸的是,观察员没有收到通知。

我的代码:

将被观察的类:

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]
    }
}

观察者视图控制器:

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
    }

谢谢!

【问题讨论】:

    标签: ios swift key-value-observing


    【解决方案1】:

    您需要在 keyPathsForValuesAffecting 方法上使用 @objc,以便 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 的建议在上游属性(namesurname)上使用 dynamic

    这是一个完整的测试程序(作为 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
    

    【讨论】:

    • 我已经用一个完整的工作测试程序更新了我的答案。
    • 请注意,您还需要将@objc 应用于您的dep 属性。如果您使用#keyPath 而不是裸字符串,编译器将指出缺少@objc 的属性。
    • 完美!您在 Playground 中的代码就像一个魅力!我用@objc 更新了我的dep 并用#keyPath 切换了String,但是对于我的VC 它仍然不起作用,你知道为什么吗?感谢您花时间帮助我。 ?
    【解决方案2】:

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

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

    【讨论】:

    • 当然 :) 我知道还有更多方法可以达到相同的结果,但对我来说至关重要的是使用 keyPathsForValuesAffecting&lt;key&gt; 方法
    【解决方案3】:

    namesurname 属性也必须是 @objc dynamic。由于您的keyPathsForValuesAffectingFullName 方法,当观察到fullName 时,KVO 会观察它们。 @objc 是让他们被观察到的必要条件。 dynamic 是让 Swift 每次设置时实际调用设置器(这是 KVO 挂钩的)所必需的,而不是内联它并仅设置支持实例变量。

    【讨论】:

    • 我也尝试使用namesurname 作为@objc dynamic... 仍然无法工作;/
    • 您已确认您的viewDidLoad 已被调用?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多