您可以在 Swift 中使用 KVO,但仅限于 dynamic 子类的 dynamic 属性。假设您想观察 Foo 类的 bar 属性。在 Swift 4 中,在 NSObject 子类中将 bar 指定为 dynamic 属性:
class Foo: NSObject {
@objc dynamic var bar = 0
}
然后您可以注册以观察bar 属性的变化。在 Swift 4 和 Swift 3.2 中,这已大大简化,如 Using Key-Value Observing in Swift 中所述:
class MyObject {
private var token: NSKeyValueObservation
var objectToObserve = Foo()
init() {
token = objectToObserve.observe(\.bar) { [weak self] object, change in // the `[weak self]` is to avoid strong reference cycle; obviously, if you don't reference `self` in the closure, then `[weak self]` is not needed
print("bar property is now \(object.bar)")
}
}
}
注意,在 Swift 4 中,我们现在使用反斜杠字符来强类型键路径(\.bar 是被观察对象的 bar 属性的键路径)。此外,因为它使用了完成闭包模式,我们不必手动移除观察者(当token 超出范围时,观察者会为我们移除),我们也不必担心调用super 实现如果密钥不匹配。仅当调用此特定观察者时才调用闭包。如需更多信息,请参阅 WWDC 2017 视频,What's New in Foundation。
在 Swift 3 中,观察这一点,它有点复杂,但与在 Objective-C 中所做的非常相似。也就是说,您将实现observeValue(forKeyPath keyPath:, of object:, change:, context:),它 (a) 确保我们正在处理我们的上下文(而不是我们的 super 实例已注册观察的内容);然后 (b) 根据需要处理它或将其传递给 super 实现。并确保在适当的时候将自己作为观察者移除。例如,你可以在观察者被释放时移除它:
在 Swift 3 中:
class MyObject: NSObject {
private var observerContext = 0
var objectToObserve = Foo()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: #keyPath(Foo.bar), 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
}
// do something upon notification of the observed object
print("\(keyPath): \(change?[.newKey])")
}
}
注意,您只能观察可以在 Objective-C 中表示的属性。因此,您无法观察到泛型、Swift struct 类型、Swift enum 类型等。
有关 Swift 2 实现的讨论,请参阅下面的原始答案。
使用dynamic 关键字实现带有NSObject 子类的KVO 在Using Swift with Cocoa and Objective 的Adopting Cocoa Design Conventions 章节的Key-Value Observing 部分进行了描述-C 指南:
键值观察是一种允许对象在其他对象的指定属性发生更改时得到通知的机制。只要类继承自 NSObject 类,就可以对 Swift 类使用键值观察。您可以使用这三个步骤在 Swift 中实现键值观察。
-
将dynamic 修饰符添加到您要观察的任何属性。有关dynamic 的更多信息,请参阅Requiring Dynamic Dispatch。
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
-
创建一个全局上下文变量。
private var myContext = 0
-
为key-path添加一个观察者,并覆盖observeValueForKeyPath:ofObject:change:context:方法,并移除deinit中的观察者。
class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("Date changed: \(newValue)")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
[注意,此 KVO 讨论随后已从 Using Swift with Cocoa and Objective-C 指南中删除,该指南已针对 Swift 3 进行了调整,但它仍然可以按照这个答案。]
值得注意的是,Swift 有自己的本机 property observer 系统,但这是针对指定其自己代码的类,该代码将在观察其自身属性时执行。另一方面,KVO 旨在注册以观察其他类的某些动态属性的变化。