NSKVOSupport: Implement legacy KVO API

Implements the setKeys:triggerChangeNotificationsForDependentKey: class
method. Please do not use it. It is fundamentally broken, and requires
the object's meta class to hold additional state.

Keys from this class method are the last resort when retrieving
dependencies via keyPathsForValuesAffectingValueForKey:.
This aligns with the implementation in Foundation.
This commit is contained in:
Hugo Melder 2025-02-04 07:34:14 +01:00
parent 732fd2d2fb
commit 8889e7ed81

View file

@ -569,9 +569,29 @@ _removeKeypathObserver(id object, NSString *keypath, id observer, void *context)
#pragma region KVO Core Implementation - NSObject category
static const char *const KVO_MAP = "_NSKVOMap";
@implementation
NSObject (NSKeyValueObserving)
+ (void) setKeys: (NSArray *) triggerKeys
triggerChangeNotificationsForDependentKey: (NSString *) dependentKey
{
NSMutableDictionary<NSString *, NSSet *> *affectingKeys;
NSSet *triggerKeySet;
affectingKeys = objc_getAssociatedObject(self, KVO_MAP);
if (nil == affectingKeys)
{
affectingKeys = [NSMutableDictionary dictionaryWithCapacity: 10];
objc_setAssociatedObject(self, KVO_MAP, affectingKeys,
OBJC_ASSOCIATION_RETAIN);
}
triggerKeySet = [NSSet setWithArray: triggerKeys];
[affectingKeys setValue: triggerKeySet forKey: dependentKey];
}
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
change: (NSDictionary<NSString *, id> *)change
@ -631,6 +651,7 @@ static void *s_kvoObservationInfoAssociationKey; // has no value; pointer used
static NSSet *emptySet = nil;
static gs_mutex_t lock = GS_MUTEX_INIT_STATIC;
NSUInteger keyLength;
NSDictionary *affectingKeys;
if (nil == emptySet)
{
@ -701,6 +722,20 @@ static void *s_kvoObservationInfoAssociationKey; // has no value; pointer used
{
return [self performSelector:sel];
}
// We compute an NSSet from information provided by previous invocations
// of the now-deprecated setKeys:triggerChangeNotificationsForDependentKey:
// if the original imp returns an empty set.
// This aligns with Apple's backwards compatibility.
affectingKeys = (NSDictionary *)objc_getAssociatedObject(self, KVO_MAP);
if (unlikely(nil != affectingKeys))
{
NSSet *set = [affectingKeys objectForKey:key];
if (set != nil)
{
return set;
}
}
}
return emptySet;
}