@@ -52,79 +52,55 @@ fileprivate extension NSObject {
52
52
// must coexist, so it was renamed. The old name must not be used in the
53
53
// new runtime.
54
54
@objc private class __KVOKeyPathBridgeMachinery : NSObject {
55
- @nonobjc static let swizzler : ( ) = {
55
+ @nonobjc static var keyPathTable : [ String : AnyKeyPath ] = {
56
56
/*
57
57
Move all our methods into place. We want the following:
58
58
__KVOKeyPathBridgeMachinery's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replaces NSObject's versions of them
59
59
NSObject's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replace NSObject's __old_unswizzled_* methods
60
60
NSObject's _old_unswizzled_* methods replace __KVOKeyPathBridgeMachinery's methods, and are never invoked
61
61
*/
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 ) {
76
62
let rootClass : AnyClass = NSObject . self
77
63
let bridgeClass : AnyClass = __KVOKeyPathBridgeMachinery. self
78
64
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
83
69
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
123
96
}
124
97
125
98
@nonobjc fileprivate static func _bridgeKeyPath( _ keyPath: String ? ) -> AnyKeyPath ? {
126
99
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
128
104
}
129
105
130
106
@objc override class func automaticallyNotifiesObservers( forKey key: String ) -> Bool {
@@ -151,85 +127,65 @@ fileprivate extension NSObject {
151
127
}
152
128
153
129
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)
156
135
}
157
136
158
137
// NOTE: older overlays called this NSKeyValueObservation. We now use
159
138
// that name in the source code, but add an underscore to the runtime
160
139
// name to avoid conflicts when both are loaded into the same process.
161
140
@objc ( _NSKeyValueObservation)
162
141
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
- }
219
142
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
+ } ( )
221
157
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 )
224
167
}
225
168
226
169
///invalidate() will be called automatically when an NSKeyValueObservation is deinited
227
170
@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)
229
185
}
230
186
231
187
deinit {
232
- invalidate ( )
188
+ object ? . removeObserver ( self , forKeyPath : path , context : nil )
233
189
}
234
190
}
235
191
@@ -241,14 +197,16 @@ extension _KeyValueCodingAndObserving {
241
197
options: NSKeyValueObservingOptions = [ ] ,
242
198
changeHandler: @escaping ( Self , NSKeyValueObservedChange < Value > ) -> Void )
243
199
-> 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
245
201
let notification = NSKeyValueObservedChange ( kind: change. kind,
246
202
newValue: change. newValue as? Value ,
247
203
oldValue: change. oldValue as? Value ,
248
204
indexes: change. indexes,
249
205
isPrior: change. isPrior)
250
206
changeHandler ( obj as! Self , notification)
251
207
}
208
+ result. start ( options)
209
+ return result
252
210
}
253
211
254
212
public func willChangeValue< Value> ( for keyPath: __owned KeyPath< Self , Value > ) {
0 commit comments