如果是这样,那么在Objective-C中使用键值观察时是否没有其他键差异?
如果是这样,那么在Objective-C中使用键值观察时是否没有其他键差异?
Answers:
(编辑以添加新信息):考虑使用Combine框架是否可以帮助您完成所需的工作,而不是使用KVO
是的,没有。KVO一直以来都在处理NSObject子类。它不适用于不继承NSObject的类。斯威夫特(目前至少)没有自己的原生观察系统。
(有关如何将其他属性公开为ObjC的信息,请参见注释,以便KVO在它们上起作用)
有关完整示例,请参阅Apple文档。
dynamic
在任何Swift类上使用关键字来启用KVO支持。
dynamic
关键字位于要使键值可观察的属性上。
dynamic
的关键字可以在苹果开发者图书馆被发现使用斯威夫特与可可和Objective-C部分。
dynamic
对于希望与KVO兼容的类中的任何属性,请使用关键字(而不是dynamic
类本身的关键字)。这对我有用!
您可以在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中,这已大大简化,如在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视频,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实现的讨论,请参阅下面的原始答案。
在使用Swift和Cocoa和Objective-C指南的“ 采用可可设计约定”一章的“ 键值观察”部分中描述了如何使用dynamic
关键字通过NSObject
子类实现KVO :
键值观察是一种机制,它允许将其他对象的指定属性的更改通知给对象。您可以对Swift类使用键值观察,只要该类从该类继承即可
NSObject
。您可以使用这三个步骤在Swift中实现键值观察。
将
dynamic
修饰符添加到要观察的任何属性。有关更多信息dynamic
,请参见要求动态调度。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与Cocoa和Objective-C结合使用”指南中删除,该指南已针对Swift 3进行了改编,但仍按此答案的顶部概述进行。]
值得注意的是,Swift具有自己的本机属性观察器系统,但这是针对一个类,该类指定了自己的代码,这些代码将在观察其自身的属性时执行。另一方面,KVO旨在注册以观察其他类的某些动态特性的变化。
myContext
?如何观察多个属性?
options
空,则仅表示change
将不包含旧值或新值(例如,您可以通过引用对象本身来自己获得新值)。如果您只指定了.new
not .old
,则表示change
仅包含新值,而不包括旧值(例如,您通常不关心旧值是什么,而只关心新值)。如果您需要observeValueForKeyPath
同时传递旧值和新值,请指定[.new, .old]
。最下面的一行options
仅指定change
字典中包含的内容。
是和否:
是的,您可以在Swift中使用相同的旧KVO API来观察Objective-C对象。
您还可以观察dynamic
继承自的Swift对象的属性NSObject
。
但是... 不,它的类型不是很严格,就像您期望的Swift本机观测系统那样。
将Swift与Cocoa和Objective-C结合使用 关键价值观察
不,目前没有针对任意Swift对象的内置值观察系统。
是的,有内置的Property Observers,它们是强类型的。
但是... 不,它们不是KVO,因为它们仅允许观察对象自身的属性,不支持嵌套观察(“键路径”),因此您必须显式实现它们。
Swift编程语言 物业观察员
是的,您可以实现显式的值观察,该观察将被强类型化,并允许从其他对象添加多个处理程序,甚至支持嵌套/“键路径”。
但是... 不,它不是KVO,因为它仅适用于您实现为可观察的属性。
您可以在此处找到用于实现此类价值观察的库:
Observable-Swift-Swift的KVO-价值观察和事件
一个示例可能会有所帮助。如果我有一个带有属性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)")
}
是。
KVO需要动态调度,因此您只需要将dynamic
修饰符添加到方法,属性,下标或初始化程序中:
dynamic var foo = 0
该dynamic
修改确保对声明的引用将被动态调度和访问过objc_msgSend
。
除了罗伯的答案。该类必须继承自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类型兼容性
如果使用的API(例如键值观察)可以动态替换方法的实现,则还可以使用dynamic修饰符要求通过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
}
}
}
目前,Swift不支持任何用于观察“ self”以外对象的属性变化的内置机制,因此不,它不支持KVO。
但是,KVO是Objective-C和Cocoa的基本组成部分,因此很有可能会在将来添加它。当前文档似乎暗示了这一点:
键值观察
即将发布的信息。
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
值得一提的是,将Xcode更新为7 beta后,您可能会收到以下消息: “方法不会覆盖其超类中的任何方法”。这是因为参数的可选性。确保观察处理程序的外观完全如下:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)
这可能对少数人有帮助-
// 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。您只需很少的更改就可以使用此代码。