我想知道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确定是否实际更改了值似乎很容易的任务?
此外,奖金问题,我如何利用对象'变量?它不会让我改变名称,当然也不喜欢带有空格的变量。
答案 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
并检查该上下文;以及
context
和change.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