Skip to content

Commit 596dfd9

Browse files
author
Joe Shajrawi
authored
Merge pull request #22297 from millenomi/revert-fix_kvo
Simulator build fix — (temporarily) revert "Merge pull request #20103 from lilyball/fix_kvo"
2 parents 7696917 + ac7a939 commit 596dfd9

File tree

4 files changed

+84
-191
lines changed

4 files changed

+84
-191
lines changed

stdlib/public/Darwin/Foundation/NSObject.swift

Lines changed: 81 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -52,79 +52,55 @@ fileprivate extension NSObject {
5252
// must coexist, so it was renamed. The old name must not be used in the
5353
// new runtime.
5454
@objc private class __KVOKeyPathBridgeMachinery : NSObject {
55-
@nonobjc static let swizzler: () = {
55+
@nonobjc static var keyPathTable: [String : AnyKeyPath] = {
5656
/*
5757
Move all our methods into place. We want the following:
5858
__KVOKeyPathBridgeMachinery's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replaces NSObject's versions of them
5959
NSObject's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replace NSObject's __old_unswizzled_* methods
6060
NSObject's _old_unswizzled_* methods replace __KVOKeyPathBridgeMachinery's methods, and are never invoked
6161
*/
62-
threeWaySwizzle(#selector(NSObject.keyPathsForValuesAffectingValue(forKey:)), with: #selector(NSObject.__old_unswizzled_keyPathsForValuesAffectingValue(forKey:)))
63-
threeWaySwizzle(#selector(NSObject.automaticallyNotifiesObservers(forKey:)), with: #selector(NSObject.__old_unswizzled_automaticallyNotifiesObservers(forKey:)))
64-
}()
65-
66-
/// Performs a 3-way swizzle between `NSObject` and `__KVOKeyPathBridgeMachinery`.
67-
///
68-
/// The end result of this swizzle is the following:
69-
/// * `NSObject.selector` contains the IMP from `__KVOKeyPathBridgeMachinery.selector`
70-
/// * `NSObject.unswizzledSelector` contains the IMP from the original `NSObject.selector`.
71-
/// * __KVOKeyPathBridgeMachinery.selector` contains the (useless) IMP from `NSObject.unswizzledSelector`.
72-
///
73-
/// This swizzle is done in a manner that modifies `NSObject.selector` last, in order to ensure thread safety
74-
/// (by the time `NSObject.selector` is swizzled, `NSObject.unswizzledSelector` will contain the original IMP)
75-
@nonobjc private static func threeWaySwizzle(_ selector: Selector, with unswizzledSelector: Selector) {
7662
let rootClass: AnyClass = NSObject.self
7763
let bridgeClass: AnyClass = __KVOKeyPathBridgeMachinery.self
7864

79-
// Swap bridge.selector <-> NSObject.unswizzledSelector
80-
let unswizzledMethod = class_getClassMethod(rootClass, unswizzledSelector)!
81-
let bridgeMethod = class_getClassMethod(bridgeClass, selector)!
82-
method_exchangeImplementations(unswizzledMethod, bridgeMethod)
65+
let dependentSel = #selector(NSObject.keyPathsForValuesAffectingValue(forKey:))
66+
let rootDependentImpl = class_getClassMethod(rootClass, dependentSel)!
67+
let bridgeDependentImpl = class_getClassMethod(bridgeClass, dependentSel)!
68+
method_exchangeImplementations(rootDependentImpl, bridgeDependentImpl) // NSObject <-> Us
8369

84-
// Swap NSObject.selector <-> NSObject.unswizzledSelector
85-
// NSObject.unswizzledSelector at this point contains the bridge IMP
86-
let rootMethod = class_getClassMethod(rootClass, selector)!
87-
method_exchangeImplementations(rootMethod, unswizzledMethod)
88-
}
89-
90-
private class BridgeKey : NSObject, NSCopying {
91-
let value: String
92-
93-
init(_ value: String) {
94-
self.value = value
95-
}
96-
97-
func copy(with zone: NSZone? = nil) -> Any {
98-
return self
99-
}
100-
101-
override func isEqual(_ object: Any?) -> Bool {
102-
return value == (object as? BridgeKey)?.value
103-
}
104-
105-
override var hash: Int {
106-
var hasher = Hasher()
107-
hasher.combine(ObjectIdentifier(BridgeKey.self))
108-
hasher.combine(value)
109-
return hasher.finalize()
110-
}
111-
}
112-
113-
/// Temporarily maps a `String` to an `AnyKeyPath` that can be retrieved with `_bridgeKeyPath(_:)`.
114-
///
115-
/// This uses a per-thread storage so key paths on other threads don't interfere.
116-
@nonobjc fileprivate static func _withBridgeableKeyPath(from keyPathString: String, to keyPath: AnyKeyPath, block: () -> Void) {
117-
_ = __KVOKeyPathBridgeMachinery.swizzler
118-
let key = BridgeKey(keyPathString)
119-
let oldValue = Thread.current.threadDictionary[key]
120-
Thread.current.threadDictionary[key] = keyPath
121-
defer { Thread.current.threadDictionary[key] = oldValue }
122-
block()
70+
let originalDependentImpl = class_getClassMethod(bridgeClass, dependentSel)! //we swizzled it onto this class, so this is actually NSObject's old implementation
71+
let originalDependentSel = #selector(NSObject.__old_unswizzled_keyPathsForValuesAffectingValue(forKey:))
72+
let dummyDependentImpl = class_getClassMethod(rootClass, originalDependentSel)!
73+
method_exchangeImplementations(originalDependentImpl, dummyDependentImpl) // NSObject's original version <-> NSObject's __old_unswizzled_ version
74+
75+
let autoSel = #selector(NSObject.automaticallyNotifiesObservers(forKey:))
76+
let rootAutoImpl = class_getClassMethod(rootClass, autoSel)!
77+
let bridgeAutoImpl = class_getClassMethod(bridgeClass, autoSel)!
78+
method_exchangeImplementations(rootAutoImpl, bridgeAutoImpl) // NSObject <-> Us
79+
80+
let originalAutoImpl = class_getClassMethod(bridgeClass, autoSel)! //we swizzled it onto this class, so this is actually NSObject's old implementation
81+
let originalAutoSel = #selector(NSObject.__old_unswizzled_automaticallyNotifiesObservers(forKey:))
82+
let dummyAutoImpl = class_getClassMethod(rootClass, originalAutoSel)!
83+
method_exchangeImplementations(originalAutoImpl, dummyAutoImpl) // NSObject's original version <-> NSObject's __old_unswizzled_ version
84+
85+
return [:]
86+
}()
87+
88+
@nonobjc static var keyPathTableLock = NSLock()
89+
90+
@nonobjc fileprivate static func _bridgeKeyPath(_ keyPath: __owned AnyKeyPath) -> String {
91+
guard let keyPathString = keyPath._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \(keyPath)") }
92+
__KVOKeyPathBridgeMachinery.keyPathTableLock.lock()
93+
defer { __KVOKeyPathBridgeMachinery.keyPathTableLock.unlock() }
94+
__KVOKeyPathBridgeMachinery.keyPathTable[keyPathString] = keyPath
95+
return keyPathString
12396
}
12497

12598
@nonobjc fileprivate static func _bridgeKeyPath(_ keyPath:String?) -> AnyKeyPath? {
12699
guard let keyPath = keyPath else { return nil }
127-
return Thread.current.threadDictionary[BridgeKey(keyPath)] as? AnyKeyPath
100+
__KVOKeyPathBridgeMachinery.keyPathTableLock.lock()
101+
defer { __KVOKeyPathBridgeMachinery.keyPathTableLock.unlock() }
102+
let path = __KVOKeyPathBridgeMachinery.keyPathTable[keyPath]
103+
return path
128104
}
129105

130106
@objc override class func automaticallyNotifiesObservers(forKey key: String) -> Bool {
@@ -151,85 +127,65 @@ fileprivate extension NSObject {
151127
}
152128

153129
func _bridgeKeyPathToString(_ keyPath:AnyKeyPath) -> String {
154-
guard let keyPathString = keyPath._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \(keyPath)") }
155-
return keyPathString
130+
return __KVOKeyPathBridgeMachinery._bridgeKeyPath(keyPath)
131+
}
132+
133+
func _bridgeStringToKeyPath(_ keyPath:String) -> AnyKeyPath? {
134+
return __KVOKeyPathBridgeMachinery._bridgeKeyPath(keyPath)
156135
}
157136

158137
// NOTE: older overlays called this NSKeyValueObservation. We now use
159138
// that name in the source code, but add an underscore to the runtime
160139
// name to avoid conflicts when both are loaded into the same process.
161140
@objc(_NSKeyValueObservation)
162141
public class NSKeyValueObservation : NSObject {
163-
// We use a private helper class as the actual observer. This lets us attach the helper as an associated object
164-
// to the object we're observing, thus ensuring the helper will still be alive whenever a KVO change notification
165-
// is broadcast, even on a background thread.
166-
//
167-
// For the associated object, we use the Helper instance itself as its own key. This guarantees key uniqueness.
168-
private class Helper : NSObject {
169-
@nonobjc weak var object : NSObject?
170-
@nonobjc let path: String
171-
@nonobjc let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void
172-
173-
// workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
174-
// specifically, overriding observeValue(forKeyPath:of:change:context:) complains that it's not Obj-C-compatible
175-
@nonobjc static let swizzler: () = {
176-
let bridgeClass: AnyClass = Helper.self
177-
let observeSel = #selector(NSObject.observeValue(forKeyPath:of:change:context:))
178-
let swapSel = #selector(Helper._swizzle_me_observeValue(forKeyPath:of:change:context:))
179-
let swapObserveMethod = class_getInstanceMethod(bridgeClass, swapSel)!
180-
class_addMethod(bridgeClass, observeSel, method_getImplementation(swapObserveMethod), method_getTypeEncoding(swapObserveMethod))
181-
}()
182-
183-
@nonobjc init(object: NSObject, keyPath: AnyKeyPath, options: NSKeyValueObservingOptions, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
184-
_ = Helper.swizzler
185-
let path = _bridgeKeyPathToString(keyPath)
186-
self.object = object
187-
self.path = path
188-
self.callback = callback
189-
super.init()
190-
objc_setAssociatedObject(object, associationKey(), self, .OBJC_ASSOCIATION_RETAIN)
191-
__KVOKeyPathBridgeMachinery._withBridgeableKeyPath(from: path, to: keyPath) {
192-
object.addObserver(self, forKeyPath: path, options: options, context: nil)
193-
}
194-
}
195-
196-
@nonobjc func invalidate() {
197-
guard let object = self.object else { return }
198-
object.removeObserver(self, forKeyPath: path, context: nil)
199-
objc_setAssociatedObject(object, associationKey(), nil, .OBJC_ASSOCIATION_ASSIGN)
200-
self.object = nil
201-
}
202-
203-
@nonobjc private func associationKey() -> UnsafeRawPointer {
204-
return UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())
205-
}
206-
207-
@objc private func _swizzle_me_observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSString : Any]?, context: UnsafeMutableRawPointer?) {
208-
guard let object = object as? NSObject, object === self.object, let change = change else { return }
209-
let rawKind:UInt = change[NSKeyValueChangeKey.kindKey.rawValue as NSString] as! UInt
210-
let kind = NSKeyValueChange(rawValue: rawKind)!
211-
let notification = NSKeyValueObservedChange(kind: kind,
212-
newValue: change[NSKeyValueChangeKey.newKey.rawValue as NSString],
213-
oldValue: change[NSKeyValueChangeKey.oldKey.rawValue as NSString],
214-
indexes: change[NSKeyValueChangeKey.indexesKey.rawValue as NSString] as! IndexSet?,
215-
isPrior: change[NSKeyValueChangeKey.notificationIsPriorKey.rawValue as NSString] as? Bool ?? false)
216-
callback(object, notification)
217-
}
218-
}
219142

220-
@nonobjc private let helper: Helper
143+
@nonobjc weak var object : NSObject?
144+
@nonobjc let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void
145+
@nonobjc let path : String
146+
147+
//workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
148+
@nonobjc static var swizzler : NSKeyValueObservation? = {
149+
let bridgeClass: AnyClass = NSKeyValueObservation.self
150+
let observeSel = #selector(NSObject.observeValue(forKeyPath:of:change:context:))
151+
let swapSel = #selector(NSKeyValueObservation._swizzle_me_observeValue(forKeyPath:of:change:context:))
152+
let rootObserveImpl = class_getInstanceMethod(bridgeClass, observeSel)!
153+
let swapObserveImpl = class_getInstanceMethod(bridgeClass, swapSel)!
154+
method_exchangeImplementations(rootObserveImpl, swapObserveImpl)
155+
return nil
156+
}()
221157

222-
fileprivate init(object: NSObject, keyPath: AnyKeyPath, options: NSKeyValueObservingOptions, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
223-
helper = Helper(object: object, keyPath: keyPath, options: options, callback: callback)
158+
fileprivate init(object: NSObject, keyPath: AnyKeyPath, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
159+
path = _bridgeKeyPathToString(keyPath)
160+
let _ = NSKeyValueObservation.swizzler
161+
self.object = object
162+
self.callback = callback
163+
}
164+
165+
fileprivate func start(_ options: NSKeyValueObservingOptions) {
166+
object?.addObserver(self, forKeyPath: path, options: options, context: nil)
224167
}
225168

226169
///invalidate() will be called automatically when an NSKeyValueObservation is deinited
227170
@objc public func invalidate() {
228-
helper.invalidate()
171+
object?.removeObserver(self, forKeyPath: path, context: nil)
172+
object = nil
173+
}
174+
175+
@objc func _swizzle_me_observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSString : Any]?, context: UnsafeMutableRawPointer?) {
176+
guard let ourObject = self.object, object as? NSObject == ourObject, let change = change else { return }
177+
let rawKind:UInt = change[NSKeyValueChangeKey.kindKey.rawValue as NSString] as! UInt
178+
let kind = NSKeyValueChange(rawValue: rawKind)!
179+
let notification = NSKeyValueObservedChange(kind: kind,
180+
newValue: change[NSKeyValueChangeKey.newKey.rawValue as NSString],
181+
oldValue: change[NSKeyValueChangeKey.oldKey.rawValue as NSString],
182+
indexes: change[NSKeyValueChangeKey.indexesKey.rawValue as NSString] as! IndexSet?,
183+
isPrior: change[NSKeyValueChangeKey.notificationIsPriorKey.rawValue as NSString] as? Bool ?? false)
184+
callback(ourObject, notification)
229185
}
230186

231187
deinit {
232-
invalidate()
188+
object?.removeObserver(self, forKeyPath: path, context: nil)
233189
}
234190
}
235191

@@ -241,14 +197,16 @@ extension _KeyValueCodingAndObserving {
241197
options: NSKeyValueObservingOptions = [],
242198
changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void)
243199
-> NSKeyValueObservation {
244-
return NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath, options: options) { (obj, change) in
200+
let result = NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath) { (obj, change) in
245201
let notification = NSKeyValueObservedChange(kind: change.kind,
246202
newValue: change.newValue as? Value,
247203
oldValue: change.oldValue as? Value,
248204
indexes: change.indexes,
249205
isPrior: change.isPrior)
250206
changeHandler(obj as! Self, notification)
251207
}
208+
result.start(options)
209+
return result
252210
}
253211

254212
public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {

stdlib/public/Darwin/Foundation/NSSet.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension Set {
1717
///
1818
/// The provided `NSSet` will be copied to ensure that the copy can
1919
/// not be mutated by other code.
20-
fileprivate init(_cocoaSet: __shared NSSet) {
20+
fileprivate init(_cocoaSet: __shared AnyObject) {
2121
assert(_isBridgedVerbatimToObjectiveC(Element.self),
2222
"Set can be backed by NSSet _variantStorage only when the member type can be bridged verbatim to Objective-C")
2323
// FIXME: We would like to call CFSetCreateCopy() to avoid doing an

stdlib/public/Darwin/Foundation/NSSortDescriptor.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,18 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
@_exported import Foundation // Clang module
14-
import ObjectiveC
1514

1615
extension NSSortDescriptor {
1716
public convenience init<Root, Value>(keyPath: KeyPath<Root, Value>, ascending: Bool) {
1817
self.init(key: _bridgeKeyPathToString(keyPath), ascending: ascending)
19-
objc_setAssociatedObject(self, UnsafeRawPointer(&associationKey), keyPath, .OBJC_ASSOCIATION_RETAIN)
2018
}
2119

2220
public convenience init<Root, Value>(keyPath: KeyPath<Root, Value>, ascending: Bool, comparator cmptr: @escaping Foundation.Comparator) {
2321
self.init(key: _bridgeKeyPathToString(keyPath), ascending: ascending, comparator: cmptr)
24-
objc_setAssociatedObject(self, UnsafeRawPointer(&associationKey), keyPath, .OBJC_ASSOCIATION_RETAIN)
2522
}
2623

2724
public var keyPath: AnyKeyPath? {
28-
return objc_getAssociatedObject(self, UnsafeRawPointer(&associationKey)) as? AnyKeyPath
25+
guard let key = self.key else { return nil }
26+
return _bridgeStringToKeyPath(key)
2927
}
3028
}
31-
32-
private var associationKey: ()?

test/stdlib/KVOKeyPaths.swift

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -108,68 +108,7 @@ print("target removed")
108108
// CHECK-NEXT: swiftValue 42, objcValue three
109109
// CHECK-NEXT: swiftValue 42, objcValue four
110110
// CHECK-NEXT: swiftValue 13, objcValue four
111-
// The next 2 logs are actually a bug and shouldn't happen
112111
// CHECK-NEXT: swiftValue 13, objcValue four
113112
// CHECK-NEXT: swiftValue 13, objcValue four
114113
// CHECK-NEXT: target removed
115114

116-
//===----------------------------------------------------------------------===//
117-
// Test NSKeyValueObservingCustomization issue with observing from the callbacks
118-
//===----------------------------------------------------------------------===//
119-
120-
class Target2 : NSObject, NSKeyValueObservingCustomization {
121-
@objc dynamic var name: String?
122-
123-
class Dummy : NSObject {
124-
@objc dynamic var name: String?
125-
}
126-
127-
// In both of the callbacks, observe another property with the same key path.
128-
// We do it in both because we're not sure which callback is invoked first.
129-
// This ensures that using KVO with key paths from one callback doesn't interfere
130-
// with the ability to look up the key path using the other.
131-
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
132-
print("keyPathsAffectingValue: key == \\.name:", key == \Target2.name)
133-
_ = Dummy().observe(\.name) { (_, _) in }
134-
return []
135-
}
136-
137-
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
138-
print("automaticallyNotifiesObservers: key == \\.name:", key == \Target2.name)
139-
_ = Dummy().observe(\.name) { (_, _) in }
140-
return true
141-
}
142-
}
143-
144-
print("registering observer for Target2")
145-
withExtendedLifetime(Target2()) { (target) in
146-
_ = target.observe(\.name) { (_, _) in }
147-
}
148-
print("observer removed")
149-
150-
// CHECK-LABEL: registering observer for Target2
151-
// CHECK-DAG: keyPathsAffectingValue: key == \.name: true
152-
// CHECK-DAG: automaticallyNotifiesObservers: key == \.name: true
153-
// CHECK-NEXT: observer removed
154-
155-
//===----------------------------------------------------------------------===//
156-
// Test NSSortDescriptor keyPath support
157-
//===----------------------------------------------------------------------===//
158-
159-
// This one doesn't really match the context of "KVO KeyPaths" but it's close enough
160-
161-
class Sortable1 : NSObject {
162-
@objc var name: String?
163-
}
164-
165-
class Sortable2 : NSObject {
166-
@objc var name: String?
167-
}
168-
169-
print("creating NSSortDescriptor")
170-
let descriptor = NSSortDescriptor(keyPath: \Sortable1.name, ascending: true)
171-
_ = NSSortDescriptor(keyPath: \Sortable2.name, ascending: true)
172-
print("keyPath == \\Sortable1.name:", descriptor.keyPath == \Sortable1.name)
173-
174-
// CHECK-LABEL: creating NSSortDescriptor
175-
// CHECK-NEXT: keyPath == \Sortable1.name: true

0 commit comments

Comments
 (0)