-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KVO not working correctly when observing NSProxy objects #472
Comments
With our codebase we’re running into another issue that may be related to the one outlined above, where the KVO implementation calls However, this issue is not reproduced with the code above. I’m guessing it may require a deeper chain of nested observers. |
I never use KVO, and am not really in a position to comment on the new libobjc2 based implementation though I can add a fix if you can supply one, but as a temporary workaround I've added a configure option --disable-newkvo to let you easily work with the old implementation. |
libs-base/Source/Additions/GSObjCRuntime.m Lines 568 to 617 in 9cdb4f9
|
Works when using 2024-12-02 14:21:21.701 a.out[73446:73446] After adding observer: obj isa=0xaaab18e288c0
2024-12-02 14:21:21.701 a.out[73446:73446] forwardInvocation: Selector: setName: target: 0xaaab18c4fd38
2024-12-02 14:21:21.701 a.out[73446:73446] Looking up instance method for cls=0xaaaae56e0238 sel=setName:
2024-12-02 14:21:21.701 a.out[73446:73446] -[GSFFIInvocation invokeWithTarget:] imp = 0xffff9eeea4dc impOrg = 0xaaaae56c25a0
_addNestedObserversAndOptionallyDependents dependents=0
2024-12-02 14:21:21.702 a.out[73446:73446] Changed <TObject: 0xaaab18c44938> 'derivedName': Derived MOO
_addNestedObserversAndOptionallyDependents dependents=0
2024-12-02 14:21:21.702 a.out[73446:73446] Changed <TObject: 0xaaab18c44938> 'name': MOO
2024-12-02 14:21:21.702 a.out[73446:73446] forwardInvocation: Selector: removeObserver:forKeyPath:context: target: 0xaaab18c4fd38
2024-12-02 14:21:21.702 a.out[73446:73446] Looking up instance method for cls=0xaaaae56e0238 sel=removeObserver:forKeyPath:context:
2024-12-02 14:21:21.702 a.out[73446:73446] -[GSFFIInvocation invokeWithTarget:] imp = 0xffff9eee616c impOrg = 0xffff9eee616c
2024-12-02 14:21:21.702 a.out[73446:73446] forwardInvocation: Selector: removeObserver:forKeyPath:context: target: 0xaaab18c4fd38
2024-12-02 14:21:21.702 a.out[73446:73446] Looking up instance method for cls=0xaaaae56e0238 sel=removeObserver:forKeyPath:context:
2024-12-02 14:21:21.702 a.out[73446:73446] -[GSFFIInvocation invokeWithTarget:] imp = 0xffff9eee616c impOrg = 0xffff9eee616c
hmelder@gnustep:~$ |
See #473 |
Turns out that when you observe a property from a NSProxy-based object in a nested keypath, i.e. As
Here a minimal reproducable: #import <Foundation/Foundation.h>
@interface Proxy : NSProxy
@property (nonatomic, strong) id object;
- (instancetype) initWithObject:(id)object;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
@end
@implementation Proxy
+ (NSSet *) keyPathsForValuesAffectingValueForKey: (NSString *)key {
NSLog(@"keyPathsForValuesAffectingValueForKey called on proxy");
return [NSSet set];
}
- (instancetype) initWithObject:(id)object {
_object = object;
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:_object];
[anInvocation invoke];
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [_object methodSignatureForSelector:aSelector];
}
@end
@interface Observee : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
- (NSString *) name;
@end
@implementation Observee
+ (NSSet *)keyPathsForValuesAffectingName {
return [NSSet setWithArray: @[ @"firstName", @"lastName" ]];
}
- (NSString *) name {
return [NSString stringWithFormat: @"%@ %@", _firstName, _lastName];
}
@end
@interface Encapsulation : NSObject
@property (nonatomic, strong) Proxy *proxy;
- (instancetype) init;
@end
@implementation Encapsulation
- (instancetype) init {
self = [super init];
if (self) {
_proxy = [[Proxy alloc] initWithObject: [Observee new]];
}
return self;
}
@end
@interface Observer : NSObject
@end
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(@"%@ - object: %@ change: %@", keyPath, object, change);
}
@end
int main(void) {
Observer *o = [Observer new];
Encapsulation *e = [Encapsulation new];
[e addObserver: o forKeyPath: @"proxy.name" options: 0 context: NULL];
[e addObserver: o forKeyPath: @"proxy.firstName" options: 0 context: NULL];
[(Observee *)e.proxy setFirstName: @"Hans"];
[(Observee *)e.proxy setLastName: @"Acker"];
[e removeObserver: o forKeyPath: @"proxy.name"];
[e removeObserver: o forKeyPath: @"proxy.firstName"];
return 0;
} Backtrace in LLDB:
Here an example where the msg send machinery calls the instance method with metaclass as receiver: #import <Foundation/Foundation.h>
@interface Proxy : NSProxy
@end
@implementation Proxy
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:nil];
[anInvocation invoke];
return;
}
@end
int main(void) {
[Proxy forwardInvocation: nil];
return 0;
}; |
Observing a property of an NSProxy object does not work with GNUstep, but works on Apple platforms.
Running
runTest
below prints the following on Apple platforms:But only this with GNUstep:
Reproducer:
The text was updated successfully, but these errors were encountered: