mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
228 lines
12 KiB
Swift
228 lines
12 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
|
|
public protocol _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
|
|
}
|
|
|
|
fileprivate 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")
|
|
}
|
|
|
|
}
|
|
|
|
@objc private class _KVOKeyPathBridgeMachinery : NSObject {
|
|
@nonobjc static var keyPathTable: [String : AnyKeyPath] = {
|
|
/*
|
|
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
|
|
*/
|
|
let rootClass: AnyClass = NSObject.self
|
|
let bridgeClass: AnyClass = _KVOKeyPathBridgeMachinery.self
|
|
|
|
let dependentSel = #selector(NSObject.keyPathsForValuesAffectingValue(forKey:))
|
|
let rootDependentImpl = class_getClassMethod(rootClass, dependentSel)!
|
|
let bridgeDependentImpl = class_getClassMethod(bridgeClass, dependentSel)!
|
|
method_exchangeImplementations(rootDependentImpl, bridgeDependentImpl) // NSObject <-> Us
|
|
|
|
let originalDependentImpl = class_getClassMethod(bridgeClass, dependentSel)! //we swizzled it onto this class, so this is actually NSObject's old implementation
|
|
let originalDependentSel = #selector(NSObject._old_unswizzled_keyPathsForValuesAffectingValue(forKey:))
|
|
let dummyDependentImpl = class_getClassMethod(rootClass, originalDependentSel)!
|
|
method_exchangeImplementations(originalDependentImpl, dummyDependentImpl) // NSObject's original version <-> NSObject's _old_unswizzled_ version
|
|
|
|
let autoSel = #selector(NSObject.automaticallyNotifiesObservers(forKey:))
|
|
let rootAutoImpl = class_getClassMethod(rootClass, autoSel)!
|
|
let bridgeAutoImpl = class_getClassMethod(bridgeClass, autoSel)!
|
|
method_exchangeImplementations(rootAutoImpl, bridgeAutoImpl) // NSObject <-> Us
|
|
|
|
let originalAutoImpl = class_getClassMethod(bridgeClass, autoSel)! //we swizzled it onto this class, so this is actually NSObject's old implementation
|
|
let originalAutoSel = #selector(NSObject._old_unswizzled_automaticallyNotifiesObservers(forKey:))
|
|
let dummyAutoImpl = class_getClassMethod(rootClass, originalAutoSel)!
|
|
method_exchangeImplementations(originalAutoImpl, dummyAutoImpl) // NSObject's original version <-> NSObject's _old_unswizzled_ version
|
|
|
|
return [:]
|
|
}()
|
|
|
|
@nonobjc static var keyPathTableLock = NSLock()
|
|
|
|
@nonobjc fileprivate static func _bridgeKeyPath(_ keyPath:AnyKeyPath) -> String {
|
|
guard let keyPathString = keyPath._kvcKeyPathString else { fatalError("Could not extract a String from KeyPath \(keyPath)") }
|
|
_KVOKeyPathBridgeMachinery.keyPathTableLock.lock()
|
|
defer { _KVOKeyPathBridgeMachinery.keyPathTableLock.unlock() }
|
|
_KVOKeyPathBridgeMachinery.keyPathTable[keyPathString] = keyPath
|
|
return keyPathString
|
|
}
|
|
|
|
@nonobjc fileprivate static func _bridgeKeyPath(_ keyPath:String?) -> AnyKeyPath? {
|
|
guard let keyPath = keyPath else { return nil }
|
|
_KVOKeyPathBridgeMachinery.keyPathTableLock.lock()
|
|
defer { _KVOKeyPathBridgeMachinery.keyPathTableLock.unlock() }
|
|
let path = _KVOKeyPathBridgeMachinery.keyPathTable[keyPath]
|
|
return path
|
|
}
|
|
|
|
@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 {
|
|
return _KVOKeyPathBridgeMachinery._bridgeKeyPath(keyPath)
|
|
}
|
|
|
|
func _bridgeStringToKeyPath(_ keyPath:String) -> AnyKeyPath? {
|
|
return _KVOKeyPathBridgeMachinery._bridgeKeyPath(keyPath)
|
|
}
|
|
|
|
public class NSKeyValueObservation : NSObject {
|
|
|
|
weak var object : NSObject?
|
|
let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void
|
|
let path : String
|
|
|
|
//workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
|
|
static var swizzler : NSKeyValueObservation? = {
|
|
let bridgeClass: AnyClass = NSKeyValueObservation.self
|
|
let observeSel = #selector(NSObject.observeValue(forKeyPath:of:change:context:))
|
|
let swapSel = #selector(NSKeyValueObservation._swizzle_me_observeValue(forKeyPath:of:change:context:))
|
|
let rootObserveImpl = class_getInstanceMethod(bridgeClass, observeSel)
|
|
let swapObserveImpl = class_getInstanceMethod(bridgeClass, swapSel)
|
|
method_exchangeImplementations(rootObserveImpl, swapObserveImpl)
|
|
return nil
|
|
}()
|
|
|
|
fileprivate init(object: NSObject, keyPath: AnyKeyPath, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) {
|
|
path = _bridgeKeyPathToString(keyPath)
|
|
let _ = NSKeyValueObservation.swizzler
|
|
self.object = object
|
|
self.callback = callback
|
|
}
|
|
|
|
fileprivate func start(_ options: NSKeyValueObservingOptions) {
|
|
object?.addObserver(self, forKeyPath: path, options: options, context: nil)
|
|
}
|
|
|
|
///invalidate() will be called automatically when an NSKeyValueObservation is deinited
|
|
public func invalidate() {
|
|
object?.removeObserver(self, forKeyPath: path, context: nil)
|
|
object = nil
|
|
}
|
|
|
|
@objc func _swizzle_me_observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSString : Any]?, context: UnsafeMutableRawPointer?) {
|
|
guard let ourObject = self.object, object as? NSObject == ourObject, 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(ourObject, notification)
|
|
}
|
|
|
|
deinit {
|
|
object?.removeObserver(self, forKeyPath: path, context: nil)
|
|
}
|
|
}
|
|
|
|
public 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 {
|
|
let result = NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath) { (obj, change) in
|
|
let notification = NSKeyValueObservedChange(kind: change.kind,
|
|
newValue: change.newValue as? Value,
|
|
oldValue: change.oldValue as? Value,
|
|
indexes: change.indexes,
|
|
isPrior: change.isPrior)
|
|
changeHandler(obj as! Self, notification)
|
|
}
|
|
result.start(options)
|
|
return result
|
|
}
|
|
|
|
public func willChangeValue<Value>(for keyPath: KeyPath<Self, Value>) {
|
|
(self as! NSObject).willChangeValue(forKey: _bridgeKeyPathToString(keyPath))
|
|
}
|
|
|
|
public func willChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: KeyPath<Self, Value>) {
|
|
(self as! NSObject).willChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
|
|
}
|
|
|
|
public func willChangeValue<Value>(for keyPath: 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: KeyPath<Self, Value>) {
|
|
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath))
|
|
}
|
|
|
|
public func didChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: KeyPath<Self, Value>) {
|
|
(self as! NSObject).didChange(changeKind, valuesAt: indexes, forKey: _bridgeKeyPathToString(keyPath))
|
|
}
|
|
|
|
public func didChangeValue<Value>(for keyPath: KeyPath<Self, Value>, withSetMutation mutation: NSKeyValueSetMutationKind, using set: Set<Value>) -> Void {
|
|
(self as! NSObject).didChangeValue(forKey: _bridgeKeyPathToString(keyPath), withSetMutation: mutation, using: set)
|
|
}
|
|
}
|
|
|
|
extension NSObject : _KeyValueCodingAndObserving {}
|