@@ -52,55 +52,79 @@ 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 var keyPathTable : [ String : AnyKeyPath ] = {
55
+ @nonobjc static let swizzler : ( ) = {
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 ) {
62
76
let rootClass : AnyClass = NSObject . self
63
77
let bridgeClass : AnyClass = __KVOKeyPathBridgeMachinery. self
64
78
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
69
-
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
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)
84
83
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
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 ( )
96
123
}
97
124
98
125
@nonobjc fileprivate static func _bridgeKeyPath( _ keyPath: String ? ) -> AnyKeyPath ? {
99
126
guard let keyPath = keyPath else { return nil }
100
- __KVOKeyPathBridgeMachinery. keyPathTableLock. lock ( )
101
- defer { __KVOKeyPathBridgeMachinery. keyPathTableLock. unlock ( ) }
102
- let path = __KVOKeyPathBridgeMachinery. keyPathTable [ keyPath]
103
- return path
127
+ return Thread . current. threadDictionary [ BridgeKey ( keyPath) ] as? AnyKeyPath
104
128
}
105
129
106
130
@objc override class func automaticallyNotifiesObservers( forKey key: String ) -> Bool {
@@ -127,65 +151,85 @@ fileprivate extension NSObject {
127
151
}
128
152
129
153
func _bridgeKeyPathToString( _ keyPath: AnyKeyPath ) -> String {
130
- return __KVOKeyPathBridgeMachinery. _bridgeKeyPath ( keyPath)
131
- }
132
-
133
- func _bridgeStringToKeyPath( _ keyPath: String ) -> AnyKeyPath ? {
134
- return __KVOKeyPathBridgeMachinery. _bridgeKeyPath ( keyPath)
154
+ guard let keyPathString = keyPath. _kvcKeyPathString else { fatalError ( " Could not extract a String from KeyPath \( keyPath) " ) }
155
+ return keyPathString
135
156
}
136
157
137
158
// NOTE: older overlays called this NSKeyValueObservation. We now use
138
159
// that name in the source code, but add an underscore to the runtime
139
160
// name to avoid conflicts when both are loaded into the same process.
140
161
@objc ( _NSKeyValueObservation)
141
162
public class NSKeyValueObservation : NSObject {
142
-
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
- } ( )
157
-
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
+ // 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
+ }
163
218
}
164
219
165
- fileprivate func start( _ options: NSKeyValueObservingOptions ) {
166
- object? . addObserver ( self , forKeyPath: path, options: options, context: nil )
220
+ @nonobjc private let helper : Helper
221
+
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)
167
224
}
168
225
169
226
///invalidate() will be called automatically when an NSKeyValueObservation is deinited
170
227
@objc public func 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)
228
+ helper. invalidate ( )
185
229
}
186
230
187
231
deinit {
188
- object ? . removeObserver ( self , forKeyPath : path , context : nil )
232
+ invalidate ( )
189
233
}
190
234
}
191
235
@@ -197,16 +241,14 @@ extension _KeyValueCodingAndObserving {
197
241
options: NSKeyValueObservingOptions = [ ] ,
198
242
changeHandler: @escaping ( Self , NSKeyValueObservedChange < Value > ) -> Void )
199
243
-> NSKeyValueObservation {
200
- let result = NSKeyValueObservation ( object: self as! NSObject , keyPath: keyPath) { ( obj, change) in
244
+ return NSKeyValueObservation ( object: self as! NSObject , keyPath: keyPath, options : options ) { ( obj, change) in
201
245
let notification = NSKeyValueObservedChange ( kind: change. kind,
202
246
newValue: change. newValue as? Value ,
203
247
oldValue: change. oldValue as? Value ,
204
248
indexes: change. indexes,
205
249
isPrior: change. isPrior)
206
250
changeHandler ( obj as! Self , notification)
207
251
}
208
- result. start ( options)
209
- return result
210
252
}
211
253
212
254
public func willChangeValue< Value> ( for keyPath: __owned KeyPath< Self , Value > ) {
0 commit comments