关键值观察(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 struct
types,Swift enum
types等。
有关Swift 2实现的讨论,请参阅下面的原始答案。
使用dynamic
关键字来实现具有NSObject
子类的KVO在“ 使用Swift with Cocoa和Objective-C指南”的“采用cocoadevise公约”一章的关键值观察部分中进行了描述:
键值观察是一种机制,允许对象通知对其他对象的指定属性的更改。 只要类inheritance自
NSObject
类,就可以使用Swift类的键值观察。 您可以使用这三个步骤来实现Swift中的键值观察。
将
dynamic
修饰符添加到要观察的任何属性。 有关dynamic
更多信息,请参阅要求dynamic分配 。class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
创build一个全局上下文variables。
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讨论后来被从使用Swift与Cocoa和Objective-C指南中删除了,这个指南已经被Swift 3修改了,但是仍然按照这个答案的顶部概括的方式工作。]
值得注意的是,Swift有自己的本地属性观察器系统,但这是一个类指定自己的代码,将观察自己的属性。 另一方面,KVO旨在注册观察某些其他职业的某些dynamic属性的变化。
是和否:
-
是的 ,您可以在Swift中使用相同的旧KVO API来观察Objective-C对象。
您也可以观察从NSObject
inheritance的Swift对象的dynamic
属性。
但是… 不,它不像你所期望的Swift原生观察系统那样是强types的。
在Cocoa和Objective-C中使用Swift 关键价值观察 -
不 ,目前没有任何Swift对象的内置值观测系统。
-
是的 ,内部属性观察者是强types的。
但是… 不,他们不是KVO,因为他们只允许观察对象自己的属性,不支持嵌套的观察(“关键path”),你必须明确地实现它们。
Swift编程语言| 物业观察员 -
是的 ,你可以实现明确的值观察,这将是强types的,并允许从其他对象添加多个处理程序,甚至支持嵌套/“键path”。
但是… 不,它不会是KVO,因为它只会用于你实施的可观察的属性。
你可以在这里find一个实现这样的值观察的库:
Swift的Observable-Swift – KVO – 价值观察与事件
一个例子可能在这里有所帮助。 如果我有一个具有属性name
和state
的类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_msgSend
dynamic分派和访问。
目前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的回答。 该类必须从NSObject
inheritance,我们有3种方法来触发属性更改
使用NSKeyValueCoding
setValue(value: AnyObject?, forKey key: String)
class MyObjectToObserve: NSObject { var myDate = NSDate() func updateDate() { setValue(NSDate(), forKey: "myDate") } }
从NSKeyValueObserving
使用willChangeValueForKey
和didChangeValueForKey
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。您可以使用此代码,但只需稍作更改。
- 设备上的iOS应用程序崩溃,dyld:Library未加载,Xcode 6 Beta
- 无法使用标识符Cell出队 – 必须为该标识符注册一个nib或一个类,或者在故事板中连接一个原型单元格
- 如何在Swift中使用NSURLSession downloadTask按顺序下载多个文件
- 如何在swift中以相反的顺序迭代循环?
- 你如何使用String.substringWithRange? (或者,Ranges如何在Swift中工作?)
- Xcode 6:如何使图像视图填充所有设备上的屏幕? 自动布局不工作?
- 如何在Swift中获得对应用程序委托的引用?
- 如何在NSURL中使用特殊字符?
- 调用performSeguewithIdentifier不会调用shouldperformseguewithIdentifier