blob: 8b714e6116f078460cc7e3db738a976728ec6e8e [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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 {
@nonobjc weak var object : NSObject?
@nonobjc let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void
@nonobjc let path : String
//workaround for <rdar://problem/31640524> Erroneous (?) error when using bridging in the Foundation overlay
@nonobjc 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
@objc 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)
}
}
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 {}