For the new project I need to use KVO, so I'm now refreshing my memory on how KVO works (with Swift specifically) and doing some experiments. Here's what I found with some questions inline. Full test code is below.
- observing the older (not type-safe) way works:
kvo.addObserver(self, forKeyPath: "property", options: [], context: nil)
...
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("got a change: \(change)")
}
- contrary to the documentation observing via the key path of the form:
\.objectToObserve.property
, i.e.:
observation = self.observe(\.objectToObserve.property, ...)
doesn't work and fails at runtime with:
Could not extract a String from KeyPath \Tester.kvo.instanceProperty
Should it work, or is it a bug in documentation?
- I was able to work around this issue by changing observation to:
observation = objectToObserve.observe(\.property, ...)
- Attempting observing a static property doesn't work:
TypeToObserve.addObserver(self, forKeyPath: "staticProperty", options: [], context: nil)
No errors, compile or runtime, just nothing happens. Is it supposed to work at all?
There are quite a few class var
properties commented as KVO observable in AVFoundation for example.
- If it supposed to work, is there a better type safe swift way of observing static properties?
Full test code
import Foundation
@objc class KVO: NSObject {
@objc dynamic static var staticProperty = 0
@objc dynamic var instanceProperty = 0
}
class Tester: NSObject {
private var observation: NSKeyValueObservation?
init(kvo: KVO) {
super.init()
// uncomment the relevant fragment:
// 1. observe instance property "old way", works:
// kvo.addObserver(self, forKeyPath: "instanceProperty", options: [.old, .new], context: nil)
// 2. observe instance property "new documented way", traps at runtime:
// 🛑 Could not extract a String from KeyPath \Tester.kvo.instanceProperty
// self.observation = observe(\.kvo.instanceProperty, options: [.old, .new]) { object, change in
// print("got a change (new documented way): \(change)")
// }
// 3. observe instance property "new better way", works:
// self.observation = kvo.observe(\.instanceProperty, options: [.old, .new]) { object, change in
// print("got a change (new better way): \(change)")
// }
// 4. observe static property - doesn't work, nothing happens:
// KVO.addObserver(self, forKeyPath: "staticProperty", options: [.old, .new], context: nil)
}
// observe instance property the old way support
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("got a change (old way): \(change)")
}
}
let kvo = KVO()
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
kvo.instanceProperty += 1
KVO.staticProperty += 1
}
let tester = Tester(kvo: kvo)
RunLoop.main.run(until: .distantFuture)