Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C KVO doesn't work with C unions

I need to observe union-typed properties on an Objective-C class using KVO, but it seems I have no luck with this. I did some experiments: everything works fine as long as I am using a C struct. As soon as I replace the struct with a union, automatic KVO doesn't work anymore (observeValueForKeyPath is not being called).

Here's my small test class:

AppDelegate.h:

#import <Cocoa/Cocoa.h>

typedef union {
    float data[3];
    struct {
        float x,y,z;
    };
} vec3union;

typedef struct {
    float x,y,z;
} vec3struct;

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;

@property (assign) vec3struct vectorStructValue;

@property (assign) vec3union vectorUnionValue;

@end

AppDelegate.m:

@implementation AppDelegate

@synthesize vectorStructValue = _vectorStructValue;
@synthesize vectorUnionValue = _vectorUnionValue;

- (void)dealloc
{
    [super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self addObserver:self forKeyPath:@"vectorStructValue" options:NSKeyValueObservingOptionNew context:nil];
    [self addObserver:self forKeyPath:@"vectorUnionValue" options:NSKeyValueObservingOptionNew context:nil];

    self.vectorStructValue = (vec3struct){1,2,3};
    self.vectorUnionValue = (vec3union){4,5,6};
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"keyPath %@ did change, object: %@", keyPath, [object description]);
}

@end

Output:

2013-01-12 17:38:26.447 KVOTest[57522:303] keyPath vectorStructValue did change, object: <AppDelegate: 0x100614200>

Am I doing something wrong or is this a bug or missing feature in the Objective-C runtime/KVO implementation?

Note: I know I can implement this manually, by overriding the property setter, but this is not the point of this question. The answer should give me an idea of why the automatic KVO doesn't work in this case.

Update: Just to make this clear, this is a simple test case comparing the KVO observer on a struct property to that on a union property. These properties are not interrelated. They have independent ivars with independent memory backing stores. You can remove the struct property and run the test, still the same result – no KVO observer event for the union property.

like image 976
starbugs Avatar asked Jan 12 '13 16:01

starbugs


1 Answers

The properties aren't related in OP's question.

I misread that in a fever induced hallucination.

Unions are just flat out busted in KVO/KVC. Leaving the text below because it is still interesting.


KVO doesn't work by watching memory or playing any such tricky shenanigans like that. It works by dynamically creating a subclass on the fly, overriding the setter method, and invoking the willChange.../didChange... methods automatically when the setter is called.

Thus, you effectively have 2 properties with 1 backing store. As far as KVO is concerned, though, they are in total isolation from each other.

What you want is dependent keys. You can use +keyPathsForValuesAffectingValueForKey: to create a dependency between the two keys such that calling either setter will trigger a change for the other property.

I don't know if it supports co-dependnence; if it supports what would effectively be a circular dependency.

Alternatively, you ought to be able to override the setter to call willChange/didChange for the other property (as well as the property being changed).


The related keys would be used if you want willChange/didChange to fire for both keys if either property changes. I.e. if you muck with the struct, the union effectively changes and observers of the union property should see a will/did change in response to setting the struct version.

I just tested it. You're right. Something is odd with unions. It is flat out broken. All of the above still remains true, but it does no good.

Radar filed: rdar://problem/13003794


Oooh... neat. KVO w/unions simply doesn't work. It appears that the runtime simply does not even recognize that the class has a key called vectorUnionValue at all.

I added:

+ (NSSet *)keyPathsForValuesAffectingVectorStructValue
{
    return [NSSet setWithObject:@"vectorUnionValue"];
}

+ (NSSet *)keyPathsForValuesAffectingVectorUnionValue
{
    return [NSSet setWithObject:@"vectorStructValue"];
}

Which caused a runtime exception:

2013-01-12 12:05:11.877 djkdfjkdfjkdf[51598:303] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AppDelegate 0x10010a520> valueForUndefinedKey:]: this class is not key value coding-compliant for the key vectorUnionValue.'
like image 166
bbum Avatar answered Sep 27 '22 23:09

bbum