Skip to content

Commit 4d96a7d

Browse files
authored
Merge pull request #24356 from zetasq/zetasq-fix-KVO
2 parents 2279570 + bf570e4 commit 4d96a7d

File tree

1 file changed

+34
-2
lines changed

1 file changed

+34
-2
lines changed

Darwin/Foundation-swiftoverlay/NSObject.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,17 @@ public class NSKeyValueObservation : NSObject {
233233
}
234234
}
235235

236+
// Used for type-erase Optional type
237+
private protocol _OptionalForKVO {
238+
static func _castForKVO(_ value: Any) -> Any?
239+
}
240+
241+
extension Optional: _OptionalForKVO {
242+
static func _castForKVO(_ value: Any) -> Any? {
243+
return value as? Wrapped
244+
}
245+
}
246+
236247
extension _KeyValueCodingAndObserving {
237248

238249
///when the returned NSKeyValueObservation is deinited or invalidated, it will stop observing
@@ -242,9 +253,30 @@ extension _KeyValueCodingAndObserving {
242253
changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void)
243254
-> NSKeyValueObservation {
244255
return NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath, options: options) { (obj, change) in
256+
257+
let converter = { (changeValue: Any?) -> Value? in
258+
if let optionalType = Value.self as? _OptionalForKVO.Type {
259+
// Special logic for keyPath having a optional target value. When the keyPath referencing a nil value, the newValue/oldValue should be in the form .some(nil) instead of .none
260+
// Solve https://bugs.swift.org/browse/SR-6066
261+
262+
// NSNull is used by KVO to signal that the keyPath value is nil.
263+
// If Value == Optional<T>.self, We will get nil instead of .some(nil) when casting Optional(<null>) directly.
264+
// To fix this behavior, we will eliminate NSNull first, then cast the transformed value.
265+
266+
if let unwrapped = changeValue {
267+
// We use _castForKVO to cast first.
268+
// If Value != Optional<NSNull>.self, the NSNull value will be eliminated.
269+
let nullEliminatedValue = optionalType._castForKVO(unwrapped) as Any
270+
let transformedOptional: Any? = nullEliminatedValue
271+
return transformedOptional as? Value
272+
}
273+
}
274+
return changeValue as? Value
275+
}
276+
245277
let notification = NSKeyValueObservedChange(kind: change.kind,
246-
newValue: change.newValue as? Value,
247-
oldValue: change.oldValue as? Value,
278+
newValue: converter(change.newValue),
279+
oldValue: converter(change.oldValue),
248280
indexes: change.indexes,
249281
isPrior: change.isPrior)
250282
changeHandler(obj as! Self, notification)

0 commit comments

Comments
 (0)