Environment
Xcode Version 10.3 (10G8), Xcode Version 11.0 beta 4 (11M374r)
Swift 5, Swift 5.1
Additional Detail from JIRA
| | |
|------------------|-----------------|
|Votes | 1 |
|Component/s | Foundation |
|Labels | Bug |
|Assignee | None |
|Priority | Medium |
md5: 54396a5f746d1959b402b2457c7ba2b1
Issue Description:
NSKeyValueObservingCustomization methods are never called if the very first observer is added using NObject.addObserver (or from Objective-C) rather than using the newer NSObject.observe API.
How to reproduce:
Given a class A with dependent keys, and an observer ObjcObserver:
struct MyStructWithPos {
var pos: CGPoint
}
class A: NSObject, NSKeyValueObservingCustomization {
var myStruct: MyStructWithPos = MyStructWithPos(pos: .zero)
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
if key == \A.pos {
return [\A.posX, \A.posY]
} else {
return []
}
}
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
return true
}
@objc dynamic var pos: CGPoint {
set {
myStruct.pos = newValue
}
get {
return myStruct.pos
}
}
@objc dynamic var posX: CGFloat {
set {
myStruct.pos.x = newValue
}
get {
return myStruct.pos.x
}
}
@objc dynamic var posY: CGFloat {
set {
myStruct.pos.y = newValue
}
get {
return myStruct.pos.y
}
}
}
class ObjcObserver: NSObject {
private static var observerContext = 0
init(a: A) {
super.init()
a.addObserver(self, forKeyPath: "pos", options: [], context: &ObjcObserver.observerContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &ObjcObserver.observerContext {
print("observed change to pos in NSObject observeValue method")
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
If NSObject.observe is called first:
let a = A()
let swiftObserver = a.observe(\A.pos) { (_, _) in
print("observed change to pos in observe block")
}
let objcObserver = ObjcObserver(a: a)
a.pos = CGPoint(x: 100, y: 100)
a.posX = 200
The output is:
observed change to pos in NSObject observeValue method
observed change to pos in observe block
observed change to pos in NSObject observeValue method
observed change to pos in observe block
If NSObject.addObserver is called first:
let a = A()
let objcObserver = ObjcObserver(a: a)
let swiftObserver = a.observe(\A.pos) { (_, _) in
print("observed change to pos in observe block")
}
a.pos = CGPoint(x: 100, y: 100)
a.posX = 200
Then we have instead have following (incorrect) output:
observed change to pos in observe block
observed change to pos in NSObject observeValue method
Workaround:
Implement the old string based keyPathsForValuesAffectingValue method in A instead:
class A {
...
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if key == #keyPath(pos) {
return [#keyPath(posX), #keyPath(posY)]
} else {
return []
}
}
...
}
Related issue:
NSKeyValueObservingCustomization doesn't play well with a class hierarchy. If you try to override the methods in a subclass you'll get an error "error: cannot override static method".
Attachment: Download
Environment
Xcode Version 10.3 (10G8), Xcode Version 11.0 beta 4 (11M374r) Swift 5, Swift 5.1Additional Detail from JIRA
| | | |------------------|-----------------| |Votes | 1 | |Component/s | Foundation | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 54396a5f746d1959b402b2457c7ba2b1Issue Description:
NSKeyValueObservingCustomization methods are never called if the very first observer is added using NObject.addObserver (or from Objective-C) rather than using the newer NSObject.observe API.
How to reproduce:
Given a class A with dependent keys, and an observer ObjcObserver:
If NSObject.observe is called first:
The output is:
If NSObject.addObserver is called first:
Then we have instead have following (incorrect) output:
Workaround:
Implement the old string based keyPathsForValuesAffectingValue method in A instead:
Related issue:
NSKeyValueObservingCustomization doesn't play well with a class hierarchy. If you try to override the methods in a subclass you'll get an error "error: cannot override static method".