我想知道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() } }
但是,编译器抱怨:“二进制运算符’!=’不能应用于两个’Any?’。操作数”
我的第二次尝试:
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版本不同的是,放置在其中时,Objective-C版本不会装箱在NSNumber中更改字典。
因此,问题是我该如何执行看似简单的任务来确定在Swift3中使用KVO是否实际更改了值?
另外,额外的问题是,如何利用“对象”变量?它不会让我更改名称,当然也不喜欢其中带有空格的变量。
以下是我最初的Swift 3答案,但是Swift 4简化了过程,无需进行任何强制转换。例如,如果你正在观察的Int属性调用bar的的foo对象:
Int
bar
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方法;
observeValue
消除了指定context和检查上下文的需要;和
context
该change.newValue和change.oldValue类型正确,省去了人工铸塑的需要。如果该属性是可选的,则可能必须安全地对其进行包装,但是不需要强制转换。
change.newValue
change.oldValue
您唯一需要注意的是确保您的闭包不会引入强大的参考周期(因此使用[weak self]模式)。
[weak self]
我最初的Swift 3答案如下。
你说:
但是,考虑到这一点,我认为这不适用于迅速对象的原语,例如Int(我认为)它们不继承自,NSObject并且与将Objective- C版本NSNumber放入更改字典时不会装箱一样。
NSObject
NSNumber
实际上,如果您查看这些值,则如果观察到的属性是an 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() } }
或将它们用作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类Int的某些dynamic属性的值,我将其强制转换为Int:
dynamic
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 object
的of是该参数的外部标签(如果您是调用此方法;在这种情况下,OS调用这个对我们来说,这样我们就不会在方法签名中使用这个外部的标签短)。的object是内部标记物(该方法本身内使用)。Swift有一段时间可以使用外部和内部参数标签,但是从Swift 3开始,它才真正包含在API中。
of
object
就何时使用此change参数而言,如果要观察多个对象的属性,并且这些对象在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
private var observerContext = 0
然后使用该上下文添加观察者:
foo.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
然后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() } }