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

时间:2016-10-19 22:57:43

标签: swift swift3 key-value-observing

我想知道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];
}

我在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()
    }
}

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

我的第二次尝试:

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()
            }
        }
    }
}

但是,考虑到这一点,我不认为这将适用于诸如Int之类的swift对象的原语(我假设)不从NSObject继承并且不像Objective-C版本那样赢得了&t放入更改字典时,将其装入NSNumber。

所以,问题是如何使用Swift3中的KVO确定是否实际更改了值似乎很容易的任务?

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

1 个答案:

答案 0 :(得分:31)

以下是我最初的Swift 3答案,但Swift 4简化了流程,无需任何铸造。例如,如果您正在观察Int对象的名为bar的{​​{1}}属性:

foo

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

  • 让您无需实施单独的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() { ... } } 方法;

  • 无需指定observeValue并检查该上下文;以及

  • contextchange.newValue输入正确,无需手动播放。如果该属性是可选的,您可能必须安全地打开它们,但不需要铸造。

唯一需要注意的是确保闭包不会引入强引用循环(因此使用change.oldValue模式)。

我原来的Swift 3答案如下。

你说:

  

但是,考虑到这一点,我认为这不适用于快速对象的原语,例如[weak self](我假设)不从Int继承而且与Objective-C不同当放入更改词典时,版本不会被装入NSObject

实际上,如果您查看这些值,如果观察到的属性是NSNumber,它确实通过字典Int

所以,你可以留在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() } }

NSNumber

或者,如果这是某些Swift类的某些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() } } 属性的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() } } 变量?它不会让我更改名称,当然也不喜欢带有空格的变量。

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

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

change

然后:

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

顺便说一句,我通常建议您使用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 } } ,例如context

private var

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

private var observerContext = 0

然后让我的foo.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext) 确保它是observeValue,而不是由其超类建立的那个:

context