Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
2025-02-04 Hugo Melder <[email protected]>

* Source/NSKVOSupport.m:
* Tests/base/NSKVOSupport/legacy.m:
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.

2025-01-04 Richard Frith-Macdonald <[email protected]>

* Headers/Foundation/NSRegularExpression.h:
Expand Down
2 changes: 1 addition & 1 deletion Headers/Foundation/NSKeyValueObserving.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ typedef NSString *NSKeyValueChangeKey;
* they should also be sent for dependentKey.
*/
+ (void) setKeys: (NSArray*)triggerKeys
triggerChangeNotificationsForDependentKey: (NSString*)dependentKey;
triggerChangeNotificationsForDependentKey: (NSString*)dependentKey __attribute__((deprecated));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rfm how can I mark a function as deprecated without breaking GCC < 3.1 choking on this header? Do we have a macro for that? OBJC_DEPRECATED is always a no-op.



#if OS_API_VERSION(MAC_OS_X_VERSION_10_5,GS_API_LATEST)
Expand Down
35 changes: 35 additions & 0 deletions Source/NSKVOSupport.m
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,29 @@ - (bool) isEmpty

#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
Expand Down Expand Up @@ -631,6 +651,7 @@ + (NSSet *) keyPathsForValuesAffectingValueForKey: (NSString *)key
static NSSet *emptySet = nil;
static gs_mutex_t lock = GS_MUTEX_INIT_STATIC;
NSUInteger keyLength;
NSDictionary *affectingKeys;

if (nil == emptySet)
{
Expand Down Expand Up @@ -701,6 +722,20 @@ + (NSSet *) keyPathsForValuesAffectingValueForKey: (NSString *)key
{
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;
}
Expand Down
Loading
Loading