-
Notifications
You must be signed in to change notification settings - Fork 10.3k
/
NSObject.swift
313 lines (266 loc) · 16.3 KB
/
NSObject.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2017 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
@_exported import Foundation // Clang module
import ObjectiveC
import _SwiftFoundationOverlayShims
// This exists to allow for dynamic dispatch on KVO methods added to NSObject.
// Extending NSObject with these methods would disallow overrides.
public protocol _KeyValueCodingAndObserving {}
extension NSObject : _KeyValueCodingAndObserving {}
public struct NSKeyValueObservedChange<Value> {
public typealias Kind = NSKeyValueChange
public let kind: Kind
///newValue and oldValue will only be non-nil if .new/.old is passed to `observe()`. In general, get the most up to date value by accessing it directly on the observed object instead.
public let newValue: Value?
public let oldValue: Value?
///indexes will be nil unless the observed KeyPath refers to an ordered to-many property
public let indexes: IndexSet?
///'isPrior' will be true if this change observation is being sent before the change happens, due to .prior being passed to `observe()`
public let isPrior:Bool
}
///Conforming to NSKeyValueObservingCustomization is not required to use Key-Value Observing. Provide an implementation of these functions if you need to disable auto-notifying for a key, or add dependent keys
public protocol NSKeyValueObservingCustomization : NSObjectProtocol {
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath>
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool
}
private extension NSObject {
@objc class func __old_unswizzled_automaticallyNotifiesObservers(forKey key: String?) -> Bool {
fatalError("Should never be reached")
}
@objc class func __old_unswizzled_keyPathsForValuesAffectingValue(forKey key: String?) -> Set<String> {
fatalError("Should never be reached")
}
}
// NOTE: older overlays called this _KVOKeyPathBridgeMachinery. The two
// must coexist, so it was renamed. The old name must not be used in the
// new runtime.
@objc private class __KVOKeyPathBridgeMachinery : NSObject {
@nonobjc static let swizzler: () = {
/*
Move all our methods into place. We want the following:
__KVOKeyPathBridgeMachinery's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replaces NSObject's versions of them
NSObject's automaticallyNotifiesObserversForKey:, and keyPathsForValuesAffectingValueForKey: methods replace NSObject's __old_unswizzled_* methods
NSObject's _old_unswizzled_* methods replace __KVOKeyPathBridgeMachinery's methods, and are never invoked
*/
threeWaySwizzle(#selector(NSObject.keyPathsForValuesAffectingValue(forKey:)), with: #selector(NSObject.__old_unswizzled_keyPathsForValuesAffectingValue(forKey:)))
threeWaySwizzle(#selector(NSObject.automaticallyNotifiesObservers(forKey:)), with: #selector(NSObject.__old_unswizzled_automaticallyNotifiesObservers(forKey:)))
}()
/// Performs a 3-way swizzle between `NSObject` and `__KVOKeyPathBridgeMachinery`.
///
/// The end result of this swizzle is the following:
/// * `NSObject.selector` contains the IMP from `__KVOKeyPathBridgeMachinery.selector`
/// * `NSObject.unswizzledSelector` contains the IMP from the original `NSObject.selector`.
/// * __KVOKeyPathBridgeMachinery.selector` contains the (useless) IMP from `NSObject.unswizzledSelector`.
///
/// This swizzle is done in a manner that modifies `NSObject.selector` last, in order to ensure thread safety
/// (by the time `NSObject.selector` is swizzled, `NSObject.unswizzledSelector` will contain the original IMP)
@nonobjc private static func threeWaySwizzle(_ selector: Selector, with unswizzledSelector: Selector) {
let rootClass: AnyClass = NSObject.self
let bridgeClass: AnyClass = __KVOKeyPathBridgeMachinery.self
// Swap bridge.selector <-> NSObject.unswizzledSelector
let unswizzledMethod = class_getClassMethod(rootClass, unswizzledSelector)!
let bridgeMethod = class_getClassMethod(bridgeClass, selector)!
method_exchangeImplementations(unswizzledMethod, bridgeMethod)
// Swap NSObject.selector <-> NSObject.unswizzledSelector
// NSObject.unswizzledSelector at this point contains the bridge IMP
let rootMethod = class_getClassMethod(rootClass, selector)!
method_exchangeImplementations(rootMethod, unswizzledMethod)
}
private class BridgeKey : NSObject, NSCopying {
let value: String
init(_ value: String) {
self.value = value
}
func copy(with zone: NSZone? = nil) -> Any {
return self
}
override func isEqual(_ object: Any?) -> Bool {
return value == (object as? BridgeKey)?.value
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(ObjectIdentifier(BridgeKey.self))
hasher.combine(value)
return hasher.finalize()
}
}
/// Temporarily maps a `String` to an `AnyKeyPath` that can be retrieved with `_bridgeKeyPath(_:)`.
///
/// This uses a per-thread storage so key paths on other threads don't interfere.
@nonobjc static func _withBridgeableKeyPath(from keyPathString: String, to keyPath: AnyKeyPath, block: () -> Void) {
_ = __KVOKeyPathBridgeMachinery.swizzler
let key = BridgeKey(keyPathString)
let oldValue = Thread.current.threadDictionary[key]
Thread.current.threadDictionary[key] = keyPath
defer { Thread.current.threadDictionary[key] = oldValue }
block()
}
@nonobjc static func _bridgeKeyPath(_ keyPath:String?) -> AnyKeyPath? {
guard let keyPath = keyPath else { return nil }
return Thread.current.threadDictionary[BridgeKey(keyPath)] as? AnyKeyPath
}
@objc override class func automaticallyNotifiesObservers(forKey key: String) -> Bool {
//This is swizzled so that it's -[NSObject automaticallyNotifiesObserversForKey:]
if let customizingSelf = self as? NSKeyValueObservingCustomization.Type, let path = __KVOKeyPathBridgeMachinery._bridgeKeyPath(key) {
return customizingSelf.automaticallyNotifiesObservers(for: path)
} else {
return self.__old_unswizzled_automaticallyNotifiesObservers(forKey: key) //swizzled to be NSObject's original implementation
}
}
@objc override class func keyPathsForValuesAffectingValue(forKey key: String?) -> Set<String> {
//This is swizzled so that it's -[NSObject keyPathsForValuesAffectingValueForKey:]
if let customizingSelf = self as? NSKeyValueObservingCustomization.Type, let path = __KVOKeyPathBridgeMachinery._bridgeKeyPath(key!) {
let resultSet = customizingSelf.keyPathsAffectingValue(for: path)
return Set(resultSet.lazy.map {
guard let str = $0._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \($0)") }
return str
})
} else {
return self.__old_unswizzled_keyPathsForValuesAffectingValue(forKey: key) //swizzled to be NSObject's original implementation
}
}
}
func _bridgeKeyPathToString(_ keyPath:AnyKeyPath) -> String {
guard let keyPathString = keyPath._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \(keyPath)") }
return keyPathString
}
// NOTE: older overlays called this NSKeyValueObservation. We now use
// that name in the source code, but add an underscore to the runtime
// name to avoid conflicts when both are loaded into the same process.
@objc(_NSKeyValueObservation)
public class NSKeyValueObservation : NSObject {
// We use a private helper class as the actual observer. This lets us attach the helper as an associated object
// to the object we're observing, thus ensuring the helper will still be alive whenever a KVO change notification
// is broadcast, even on a background thread.
//
// For the associated object, we use the Helper instance itself as its own key. This guarantees key uniqueness.
private class Helper : NSObject {
@nonobjc weak var object : NSObject?
@nonobjc let path: String
@nonobjc let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void
// workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
// specifically, overriding observeValue(forKeyPath:of:change:context:) complains that it's not Obj-C-compatible
@nonobjc static let swizzler: () = {
let cls = NSClassFromString("_NSKVOCompatibility") as? _NSKVOCompatibilityShim.Type
cls?._noteProcessHasUsedKVOSwiftOverlay()
let bridgeClass: AnyClass = Helper.self
let observeSel = #selector(NSObject.observeValue(forKeyPath:of:change:context:))
let swapSel = #selector(Helper._swizzle_me_observeValue(forKeyPath:of:change:context:))
let swapObserveMethod = class_getInstanceMethod(bridgeClass, swapSel)!
class_addMethod(bridgeClass, observeSel, method_getImplementation(swapObserveMethod), method_getTypeEncoding(swapObserveMethod))
}()
@nonobjc init(object: NSObject, keyPath: AnyKeyPath, options: NSKeyValueObservingOptions, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
_ = Helper.swizzler
let path = _bridgeKeyPathToString(keyPath)
self.object = object
self.path = path
self.callback = callback
super.init()
objc_setAssociatedObject(object, associationKey(), self, .OBJC_ASSOCIATION_RETAIN)
__KVOKeyPathBridgeMachinery._withBridgeableKeyPath(from: path, to: keyPath) {
object.addObserver(self, forKeyPath: path, options: options, context: nil)
}
}
@nonobjc func invalidate() {
guard let object = self.object else { return }
object.removeObserver(self, forKeyPath: path, context: nil)
objc_setAssociatedObject(object, associationKey(), nil, .OBJC_ASSOCIATION_ASSIGN)
self.object = nil
}
@nonobjc private func associationKey() -> UnsafeRawPointer {
return UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())
}
@objc private func _swizzle_me_observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSString : Any]?, context: UnsafeMutableRawPointer?) {
guard let object = object as? NSObject, object === self.object, let change = change else { return }
let rawKind:UInt = change[NSKeyValueChangeKey.kindKey.rawValue as NSString] as! UInt
let kind = NSKeyValueChange(rawValue: rawKind)!
let notification = NSKeyValueObservedChange(kind: kind,
newValue: change[NSKeyValueChangeKey.newKey.rawValue as NSString],
oldValue: change[NSKeyValueChangeKey.oldKey.rawValue as NSString],
indexes: change[NSKeyValueChangeKey.indexesKey.rawValue as NSString] as! IndexSet?,
isPrior: change[NSKeyValueChangeKey.notificationIsPriorKey.rawValue as NSString] as? Bool ?? false)
callback(object, notification)
}
}
@nonobjc private let helper: Helper
fileprivate init(object: NSObject, keyPath: AnyKeyPath, options: NSKeyValueObservingOptions, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
helper = Helper(object: object, keyPath: keyPath, options: options, callback: callback)
}
///invalidate() will be called automatically when an NSKeyValueObservation is deinited
@objc public func invalidate() {
helper.invalidate()
}
deinit {
invalidate()
}
}
// Used for type-erase Optional type
private protocol _OptionalForKVO {
static func _castForKVO(_ value: Any) -> Any?
}
extension Optional: _OptionalForKVO {
static func _castForKVO(_ value: Any) -> Any? {
return value as? Wrapped
}
}
extension _KeyValueCodingAndObserving {
///when the returned NSKeyValueObservation is deinited or invalidated, it will stop observing
public func observe<Value>(
_ keyPath: KeyPath<Self, Value>,
options: NSKeyValueObservingOptions = [],
changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void)
-> NSKeyValueObservation {
return NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath, options: options) { (obj, change) in
let converter = { (changeValue: Any?) -> Value? in
if let optionalType = Value.self as? _OptionalForKVO.Type {
// 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
// Solve https://bugs.swift.org/browse/SR-6066
// NSNull is used by KVO to signal that the keyPath value is nil.
// If Value == Optional<T>.self, We will get nil instead of .some(nil) when casting Optional(<null>) directly.
// To fix this behavior, we will eliminate NSNull first, then cast the transformed value.
if let unwrapped = changeValue {
// We use _castForKVO to cast first.
// If Value != Optional<NSNull>.self, the NSNull value will be eliminated.
let nullEliminatedValue = optionalType._castForKVO(unwrapped) as Any
let transformedOptional: Any? = nullEliminatedValue
return transformedOptional as? Value
}
}
return changeValue as? Value
}
let notification = NSKeyValueObservedChange(kind: change.kind,
newValue: converter(change.newValue),
oldValue: converter(change.oldValue),
indexes: change.indexes,
isPrior: change.isPrior)
changeHandler(obj as! Self, notification)
}
}
public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}
public func willChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}
public func willChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
public func didChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
}
public func didChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<Self, Value>) {
(self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
}
public func didChangeValue<Value>(for keyPath: __owned KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
}
}