如果是这样,在Objective-C中使用键值观察时是否存在其他不存在的关键差异?
答案 0 :(得分:139)
您可以在Swift中使用KVO,但仅适用于dynamic
子类的NSObject
属性。考虑您想要观察bar
类的Foo
属性。在Swift 4中,在bar
子类中将dynamic
指定为NSObject
属性:
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,在的Adopting Cocoa Design Conventions章节的键值观察部分中进行了描述使用Swift与Cocoa和Objective-C 指南:
键值观察是一种机制,允许对象通知其他对象的指定属性的更改。只要该类继承自
NSObject
类,您就可以将键值观察与Swift类一起使用。您可以使用这三个步骤在Swift中实现键值观察。
将
dynamic
修饰符添加到您要观察的任何属性中。有关dynamic
的详细信息,请参阅Requiring Dynamic Dispatch。class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
创建全局上下文变量。
private var myContext = 0
- 醇>
为关键路径添加观察者,并覆盖
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 with Cocoa和Objective-C 指南中删除,该指南已针对Swift 3进行了调整,但仍然可以按照这个答案。]
值得注意的是Swift有自己的原生property observer系统,但是这个类指定了自己的代码,这些代码将在观察自己的属性时执行。另一方面,KVO旨在注册以观察某些其他类的某些动态属性的变化。
答案 1 :(得分:101)
是和否。 KVO对NSObject子类的工作原理与它一如既往。它不适用于没有NSObject子类的类。斯威夫特(目前至少)没有自己的原生观察系统。
(请参阅有关如何将其他属性公开为ObjC的注释,以便KVO对其进行处理)
有关完整示例,请参阅Apple Documentation。
答案 2 :(得分:91)
是和否:
是,您可以在Swift中使用相同的旧KVO API来观察Objective-C对象。
您还可以观察从dynamic
继承的Swift对象的NSObject
属性
但是...... 没有它没有强烈打字,因为你可以期待Swift原生观察系统。
Using Swift with Cocoa and Objective-C | Key Value Observing
否,目前没有针对任意Swift对象的内置值观察系统。
是,内置 Property Observers ,是强类型的。
但是...... 否它们不是KVO,因为它们只允许观察对象自己的属性,不支持嵌套观察(“关键路径”),你必须明确地实现它们。登记/>
The Swift Programming Language | Property Observers
是,您可以实现显式值观察,它将是强类型的,并允许从其他对象添加多个处理程序,甚至支持嵌套/“键路径”。 />
但是...... 否它不会是KVO,因为它只适用于您实现为可观察的属性。
你可以在这里找到一个实现这种价值观察的图书馆:
Observable-Swift - KVO for Swift - Value Observing and Events
答案 3 :(得分:10)
一个例子可能对此有所帮助。如果我有一个model
类的实例Model
,其属性为name
和state
,我可以通过以下方式观察这些属性:
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)")
}
答案 4 :(得分:8)
是
KVO需要动态调度,因此您只需将dynamic
修饰符添加到方法,属性,下标或初始值设定项中:
dynamic var foo = 0
dynamic
修饰符可确保通过objc_msgSend
动态调度和访问对声明的引用。
答案 5 :(得分:5)
目前Swift不支持任何内置机制来观察“self”以外的对象的属性更改,所以不,它不支持KVO。
然而,KVO是Objective-C和Cocoa的基本组成部分,它很可能会在将来添加。目前的文件似乎暗示了这一点:
键值观察
即将发布的信息。
答案 6 :(得分:5)
除了Rob的回答。该类必须继承自NSObject
,我们有3种方法可以触发属性更改
使用setValue(value: AnyObject?, forKey key: String)
NSKeyValueCoding
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
setValue(NSDate(), forKey: "myDate")
}
}
使用willChangeValueForKey
didChangeValueForKey
和NSKeyValueObserving
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
willChangeValueForKey("myDate")
myDate = NSDate()
didChangeValueForKey("myDate")
}
}
使用dynamic
。见Swift Type Compatibility
如果您正在使用像键值观察这样动态替换方法实现的API,您还可以使用动态修饰符来要求通过Objective-C运行时动态调度成员访问。
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
使用时会调用属性getter和setter。您可以验证何时使用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
}
}
}
答案 7 :(得分:4)
需要提及的一件重要事项是,在将 Xcode 更新为 7 beta 后,您可能会收到以下消息: “方法不会覆盖其超类中的任何方法”。那是因为论证的选择性。确保您的观察处理程序看起来完全如下:
./
答案 8 :(得分:3)
这可能对少数人有用 -
// 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。您可以使用此代码进行少量更改。
答案 9 :(得分:1)
遇到Int等类型问题的人的另一个例子?和CGFloat?您只需将您的类设置为NSObject的子类,并按如下方式声明您的变量,例如:
class Theme : NSObject{
dynamic var min_images : Int = 0
dynamic var moreTextSize : CGFloat = 0.0
func myMethod(){
self.setValue(value, forKey: "\(min_images)")
}
}
答案 10 :(得分:1)
可以使用 Combine
而不使用NSObject
或Objective-C
可用性::iOS 13.0+
,macOS 10.15+
,tvOS 13.0+
,watchOS 6.0+
,Mac Catalyst 13.0+
,Xcode 11.0+
注意:仅需要用于不具有值类型的类。
快速版本:5.1.2
import Combine //Combine Framework
//Needs to be a class doesn't work with struct and other value types
class Car {
@Published var price : Int = 10
}
let car = Car()
//Option 1: Automatically Subscribes to the publisher
let cancellable1 = car.$price.sink {
print("Option 1: value changed to \($0)")
}
//Option 2: Manually Subscribe to the publisher
//Using this option multiple subscribers can subscribe to the same publisher
let publisher = car.$price
let subscriber2 : Subscribers.Sink<Int, Never>
subscriber2 = Subscribers.Sink(receiveCompletion: { print("completion \($0)")}) {
print("Option 2: value changed to \($0)")
}
publisher.subscribe(subscriber2)
//Assign a new value
car.price = 20
Option 1: value changed to 10
Option 2: value changed to 10
Option 1: value changed to 20
Option 2: value changed to 20