关键值观察(KVO)在Swift中是否可用?

如果是这样的话,在Objective-C中使用键值观察时,是否存在其他不存在的关键差异?

是和不是。 KVO在NSObject子类上的工作方式与以往一样。 它不适用于不具有NSObject子类的类。 斯威夫特(目前至less)没有自己的本地观测系统。

(有关如何将其他属性公开为ObjC的说明,请参见KVO)

请参阅Apple文档以获取完整示例。

你可以在Swift中使用KVO,但只能用于NSObject子类的dynamic属性。 考虑你想观察Foo类的bar属性。 在Swift 4中,将bar指定为NSObject子类中的dynamic属性:

 class Foo: NSObject { @objc dynamic var bar = 0 } 

然后您可以注册以观察bar属性的更改。 在Swift 4和Swift 3.2中,这已经大大简化了:

 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属性的关键path)对键path进行了强types化。 另外,因为它使用了完成闭包模式,所以我们不必手动删除观察者(当token超出范围时,观察者就会被移除),如果关键字没有被调用,也不必担心调用super实现不匹配。 只有当这个特定的观察者被调用时才会调用闭包。 欲了解更多信息,请参阅WWDC 2017video, 基金会的新function 。

在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中表示的属性。 因此,你不能观察generics,Swift structtypes,Swift enumtypes等。

有关Swift 2实现的讨论,请参阅下面的原始答案。


使用dynamic关键字来实现具有NSObject子类的KVO在“ 使用Swift with Cocoa和Objective-C指南”的“采用cocoadevise公约”一章的关键值观察部分中进行了描述:

键值观察是一种机制,允许对象通知对其他对象的指定属性的更改。 只要类inheritance自NSObject类,就可以使用Swift类的键值观察。 您可以使用这三个步骤来实现Swift中的键值观察。

  1. dynamic修饰符添加到要观察的任何属性。 有关dynamic更多信息,请参阅要求dynamic分配 。

     class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } } 
  2. 创build一个全局上下文variables。

     private var myContext = 0 
  3. 为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讨论后来被从使用Swift与Cocoa和Objective-C指南中删除了,这个指南已经被Swift 3修改了,但是仍然按照这个答案的顶部概括的方式工作。]


值得注意的是,Swift有自己的本地属性观察器系统,但这是一个类指定自己的代码,将观察自己的属性。 另一方面,KVO旨在注册观察某些其他职业的某些dynamic属性的变化。

是和否:

  • 是的 ,您可以在Swift中使用相同的旧KVO API来观察Objective-C对象。
    您也可以观察从NSObjectinheritance的Swift对象的dynamic属性。
    但是… 不,它不像你所期望的Swift原生观察系统那样是强types的。
    在Cocoa和Objective-C中使用Swift 关键价值观察

  • ,目前没有任何Swift对象的内置值观测系统。

  • 是的 ,内部属性观察者是强types的。
    但是… 不,他们不是KVO,因为他们只允许观察对象自己的属性,不支持嵌套的观察(“关键path”),你必须明确地实现它们。
    Swift编程语言| 物业观察员

  • 是的 ,你可以实现明确的值观察,这将是强types的,并允许从其他对象添加多个处理程序,甚至支持嵌套/“键path”。
    但是… 不,它不会是KVO,因为它只会用于你实施的可观察的属性。
    你可以在这里find一个实现这样的值观察的库:
    Swift的Observable-Swift – KVO – 价值观察与事件

一个例子可能在这里有所帮助。 如果我有一个具有属性namestate的类Model的实例model ,我可以通过以下方法观察这些属性:

 let options = NSKeyValueObservingOptions([.New, .Old, .Initial, .Prior]) model.addObserver(self, forKeyPath: "name", options: options, context: nil) model.addObserver(self, forKeyPath: "state", options: options, context: nil) 

对这些属性的更改将触发以下操作:

 override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: NSDictionary!, context: CMutableVoidPointer) { println("CHANGE OBSERVED: \(change)") } 

是。

KVO需要dynamic分派,所以你只需要将dynamic修饰符添加到方法,属性,下标或初始值设定项中:

dynamic var foo = 0

dynamic修饰符可确保对声明的引用将通过objc_msgSenddynamic分派和访问。

目前Swift不支持任何观察“自我”以外的对象属性更改的机制,所以不支持KVO。

然而,KVO是Objective-C和Cocoa的基本组成部分,看起来很有可能在将来被添加。 目前的文件似乎暗示了这一点:

键值观察

信息即将出版。

在Cocoa和Objective-C中使用Swift

需要提到的一件重要的事情是,在将Xcode更新为7testing版之后,您可能会收到以下消息: “方法不会覆盖其超类中的任何方法” 。 这是因为这个论点的select性。 确保你的观察处理程序看起来如下所示:

 override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>) 

除了Rob的回答。 该类必须从NSObjectinheritance,我们有3种方法来触发属性更改

使用NSKeyValueCoding setValue(value: AnyObject?, forKey key: String)

 class MyObjectToObserve: NSObject { var myDate = NSDate() func updateDate() { setValue(NSDate(), forKey: "myDate") } } 

NSKeyValueObserving使用willChangeValueForKeydidChangeValueForKey

 class MyObjectToObserve: NSObject { var myDate = NSDate() func updateDate() { willChangeValueForKey("myDate") myDate = NSDate() didChangeValueForKey("myDate") } } 

使用dynamic 。 请参阅Swifttypes兼容性

如果您使用API​​(如dynamicreplace方法的实现的键值观察)的API,还可以使用dynamic修饰符来要求通过Objective-C运行时dynamic分配对成员的访问。

 class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } } 

使用时会调用属性getter和setter。 您可以validation何时使用KVO。 这是一个计算属性的例子

 class MyObjectToObserve: NSObject { var backing: NSDate = NSDate() dynamic var myDate: NSDate { set { print("setter is called") backing = newValue } get { print("getter is called") return backing } } } 

任何人遇到一个像Inttypes的问题的另一个例子? 和CGFloat ?. 您只需将您的类设置为NSObject的子类,并按如下所示声明您的variables,例如:

 class Theme : NSObject{ dynamic var min_images : Int = 0 dynamic var moreTextSize : CGFloat = 0.0 func myMethod(){ self.setValue(value, forKey: "\(min_images)") } } 

这可能对less数人有用 –

 // MARK: - KVO var observedPaths: [String] = [] func observeKVO(keyPath: String) { observedPaths.append(keyPath) addObserver(self, forKeyPath: keyPath, options: [.old, .new], context: nil) } func unObserveKVO(keyPath: String) { if let index = observedPaths.index(of: keyPath) { observedPaths.remove(at: index) } removeObserver(self, forKeyPath: keyPath) } func unObserveAllKVO() { for keyPath in observedPaths { removeObserver(self, forKeyPath: keyPath) } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if let keyPath = keyPath { switch keyPath { case #keyPath(camera.iso): slider.value = camera.iso default: break } } } 

我在Swift 3中以这种方式使用了KVO。您可以使用此代码,但只需稍作更改。