检查是否使用Swift 3中的KVO更改了值

aep*_*yus 18 key-value-observing swift swift3

我想知道什么时候Swift对象的一组属性发生了变化.以前,我已经在Objective-C中实现了这个,但是我在将它转换为Swift时遇到了一些困难.

我以前的Objective-C代码是:

- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    if (![change[@"new"] isEqual:change[@"old"]])
        [self edit];
}
Run Code Online (Sandbox Code Playgroud)

我在Swift解决方案中的第一次传递是:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if change?[.newKey] != change?[.oldKey] {    // Compiler: "Binary operator '!=' cannot be applied to two 'Any?' operands"
        edit()
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,编译器抱怨:"二元运算符'!='不能应用于两个'任何?' 操作数"

我的第二次尝试:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? NSObject {
        if let oldValue = change?[.oldKey] as? NSObject {
            if !newValue.isEqual(oldValue) {
                edit()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,考虑到这一点,我认为这不适用于快速对象的原语,例如Int(我假设)不从NSObject继承而且与Objective-C版本不同,当放入时不会将其装入NSNumber改变字典.

那么,问题是我如何使用Swift3中的KVO来确定是否实际更改了一个看似简单的任务?

另外,奖金问题,我如何利用'of object'变量?它不会让我改变名称,当然也不喜欢带有空格的变量.

Rob*_*Rob 32

下面是我原来的Swift 3答案,但Swift 4简化了流程,无需任何铸造.例如,如果您正在观察对象Int调用barfoo属性:

class Foo: NSObject {
    @objc dynamic var bar: Int = 42
}

class ViewController: UIViewController {

    let foo = Foo()
    var token: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        token = foo.observe(\.bar, options: [.new, .old]) { [weak self] object, change in
            if change.oldValue != change.newValue {
                self?.edit()
            }
        }
    }

    func edit() { ... }
}
Run Code Online (Sandbox Code Playgroud)

注意,这种基于闭包的方法:

  • 让您无需实现单独的observeValue方法;

  • 消除了指定context和检查上下文的需要; 和

  • change.newValuechange.oldValue类型正确,省去了人工铸塑的需要.如果属性是可选的,您可能必须安全地打开它们,但不需要铸造.

您唯一需要注意的是确保闭包不会引入强引用循环(因此使用[weak self]模式).


我原来的Swift 3答案如下.


你说:

但是,在考虑这个问题时,我认为这不适用于swift对象的原语,例如Int哪些(我假设)不继承NSObject而且与Objective-C版本不同,NSNumber当放入更改字典时不会被装入.

实际上,如果你看一下这些值,如果观察到的属性是一个Int,它确实通过字典作为a NSNumber.

所以,你可以留在这个NSObject世界:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? NSObject,
        let oldValue = change?[.oldKey] as? NSObject,
        !newValue.isEqual(oldValue) {
            edit()
    }
}
Run Code Online (Sandbox Code Playgroud)

或者将它们用作NSNumber:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? NSNumber,
        let oldValue = change?[.oldKey] as? NSNumber,
        newValue.intValue != oldValue.intValue {
            edit()
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,如果这是一些Swift类Int的某些dynamic属性的值,我会继续将它们转换为Int:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let newValue = change?[.newKey] as? Int, let oldValue = change?[.oldKey] as? Int, newValue != oldValue {
        edit()
    }
}
Run Code Online (Sandbox Code Playgroud)

您询问:

另外,奖金问题,我如何利用of object变量?它不会让我改变名称,当然也不喜欢带有空格的变量.

of是此参数的外部标签(如果您调用此方法时使用;在这种情况下,操作系统会为我们调用此标记,因此我们不在方法签名中使用此外部标签).它object是内部标签(在方法本身中使用).Swift已经具备了一段时间内外部和内部标签参数的功能,但它只是在Swift 3中被真正包含在API中.

就使用此change参数的时间而言,如果您正在观察多个对象的属性,并且这些对象需要对KVO进行不同的处理,则使用它,例如:

foo.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
baz.addObserver(self, forKeyPath: #keyPath(Foo.qux), options: [.new, .old], context: &observerContext)
Run Code Online (Sandbox Code Playgroud)

然后:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &observerContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if (object as? Foo) == foo {
        // handle `foo` related notifications here
    }
    if (object as? Baz) == baz {
        // handle `baz` related notifications here
    }
}
Run Code Online (Sandbox Code Playgroud)

顺便说context一句,我一般建议使用,例如,有一个private var:

private var observerContext = 0
Run Code Online (Sandbox Code Playgroud)

然后使用该上下文添加观察者:

foo.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
Run Code Online (Sandbox Code Playgroud)

然后让我observeValue确定它是它的context,而不是由它的超类建立的:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &observerContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if let newValue = change?[.newKey] as? Int, let oldValue = change?[.oldKey] as? Int, newValue != oldValue {
        edit()
    }
}
Run Code Online (Sandbox Code Playgroud)