在Swift 3中使用KVO检查值是否已更改 [英] Checking if a value is changed using KVO in Swift 3

查看:76
本文介绍了在Swift 3中使用KVO检查值是否已更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道Swift对象的一组属性何时更改.以前,我已经在Objective-C中实现了此功能,但在将其转换为Swift时遇到了一些困难.

I would like to know when a set of properties of a Swift object changes. Previously, I had implemented this in Objective-C, but I'm having some difficulty converting it to Swift.

我以前的Objective-C代码是:

My previous Objective-C code is:

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

我对Swift解决方案的第一遍是:

My first pass at a Swift solution was:

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

但是,编译器抱怨:二进制运算符'!='不能应用于两个'Any?'.操作数"

However, the compiler complains: "Binary operator '!=' cannot be applied to two 'Any?' operands"

第二次尝试:

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之类的快速对象的原语(我假设这些原语不是从NSObject继承的,并且不同于Objective-C版本不会装箱到NSNumber中)当放入更改字典中时.

But, in thinking about this, I don't think this will work for primitives of swift objects such as Int which (I assume) do not inherit from NSObject and unlike the Objective-C version won't be boxed into NSNumber when placed into the change dictionary.

所以,问题是我该如何执行看似简单的任务来确定在Swift3中使用KVO是否实际更改了值?

So, the question is how do I do the seemingly easy task of determining if a value is actually being changed using the KVO in Swift3?

还有,奖励问题,如何使用对象"变量?它不会让我更改名称,当然也不喜欢其中带有空格的变量.

Also, bonus question, how do I make use of the 'of object' variable? It won't let me change the name and of course doesn't like variables with spaces in them.

推荐答案

下面是我最初的Swift 3答案,但是Swift 4简化了过程,不需要任何强制转换.例如,如果要观察foo对象的称为barInt属性:

Below is my original Swift 3 answer, but Swift 4 simplifies the process, eliminating the need for any casting. For example, if you are observing the Int property called bar of the foo object:

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

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

Note, this closure based approach:

  • 使您无需实施单独的observeValue方法;

无需指定context并检查该上下文;和

Eliminates the need for specifying a context and checking that context; and

change.newValuechange.oldValue的键入正确,无需手动转换.如果该属性是可选属性,则可能必须安全地对其进行包装,但是不需要强制转换.

The change.newValue and change.oldValue are properly typed, eliminating the need for manual casting. If the property was an optional, you may have to safely unwrap them, but no casting is needed.

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

The only thing you need to be careful about is making sure your closure doesn't introduce a strong reference cycle (hence the use of [weak self] pattern).

下面是我最初的Swift 3答案.

My original Swift 3 answer is below.

您说:

但是,考虑到这一点,我认为这不适用于诸如Int之类的快速对象的原语,这些对象(我假设)不会从NSObject继承,并且与Objective-C版本不同在放入更改字典时,可以装在NSNumber中.

But, in thinking about this, I don't think this will work for primitives of swift objects such as Int which (I assume) do not inherit from NSObject and unlike the Objective-C version won't be boxed into NSNumber when placed into the change dictionary.

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

Actually, if you look at those values, if the observed property is an Int, it does come through the dictionary as a NSNumber.

因此,您可以留在NSObject世界中:

So, you can either stay in the NSObject world:

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:

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

或者,如果这是某些Swift类的dynamic属性的Int值,我将其强制转换为Int:

Or, I'd if this was an Int value of some dynamic property of some Swift class, I'd go ahead and cast them as 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()
    }
}


您问:


You asked:

另外,还有一个额外的问题,我如何使用of object变量?它不会让我更改名称,当然也不喜欢其中带有空格的变量.

Also, bonus question, how do I make use of the of object variable? It won't let me change the name and of course doesn't like variables with spaces in them.

of是此参数的外部标签(在您调用此方法时使用;在这种情况下,操作系统会为我们调用此标签,因此我们不要在方法签名中使用此外部标签. ). object是内部标签(在方法本身内使用). Swift已有一段时间可以为参数使用外部和内部标签,但是从Swift 3开始,它才真正包含在API中.

The of is the external label for this parameter (used when if you were calling this method; in this case, the OS calls this for us, so we don't use this external label short of in the method signature). The object is the internal label (used within the method itself). Swift has had the capability for external and internal labels for parameters for a while, but it's only been truly embraced in the API as of Swift 3.

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

In terms of when you use this change parameter, you use it if you're observing the properties of more than one object, and if these objects need different handling on the KVO, e.g.:

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:


As an aside, I'd generally recommend using the context, e.g., have a private var:

private var observerContext = 0

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

And then add the observer using that context:

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

然后让我的observeValue确保它是它的context,而不是它的超类建立的一个:

And then have my observeValue make sure it was its context, and not one established by its superclass:

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

这篇关于在Swift 3中使用KVO检查值是否已更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆