Search code examples
iosobjective-cintrospectionkey-value-coding

Reading union properties using KVC in Objective-C


Update:

I have boiled the issue down to simply not being able to use key value coding on a class I made seen below

#import <Foundation/Foundation.h>
#import <GLKit/GLKit.h>

@interface CMTransformation : NSObject

@property(nonatomic) GLKVector3 position;
@property(nonatomic) GLKVector3 scale;
@property(nonatomic) GLKVector3 rotation;
@property(nonatomic) GLKVector3 anchor;

@property(nonatomic) GLKMatrix4 matrix;

- (GLKMatrix4)calculateMatrixWithParentTransformation:(CMTransformation *)parentTransformation;

@end

It has been my understanding and expience that I should be able to grab non NSObjects out as NSValues, however, I am finding it impossible to access these items (which are defined as unions) using KVC syntax:

CMTransformation* trans = [[CMTransformation alloc] init];
temp = [trans valueForKey:@"position"];

Similarly, if I try to access the underlying variable:

CMTransformation* trans = [[CMTransformation alloc] init];
temp = [trans valueForKey:@"_position"];

Both of these throw an exception because the key is not found. What am I missing here?

Previous Question

I have written some code which allows me to access a (somewhat) arbitrary structure with a string such as "transformation.position"

For some reason the code stops working on the second jump when I am trying to read a property from an NSObject. Here is the

NSString* property = actionDetails[@"Property"];
PropertyParts = [[property componentsSeparatedByString:@"."] mutableCopy];
int count = [PropertyParts count];
id current_object = initial_object;

for(int i = 0; i < count; i++)
{
    NSString* current_part = PropertyParts[i];        
    current_object = [current_object valueForKey:current_part];
}

I have tried all possible syntax for property access including Property, property and _property.

Here is the custom NSObject declaration

#import <Foundation/Foundation.h>
#import <GLKit/GLKit.h>

@interface CMTransformation : NSObject

@property(nonatomic) GLKVector3 position;
@property(nonatomic) GLKVector3 scale;
@property(nonatomic) GLKVector3 rotation;
@property(nonatomic) GLKVector3 anchor;

@property(nonatomic) GLKMatrix4 matrix;

- (GLKMatrix4)calculateMatrixWithParentTransformation:(CMTransformation *)parentTransformation;

@end

Additionally, I can see after the first loop that the debugger says that CMTransformation* is populating currrent_object, so I am at a loss as to why I can't access its properties?


Solution

  • Would you be satisfied with implementing valueForUndefinedKey: and setValue:forUndefinedKey:?

    If so, this works:

    union Test
    {
        CGFloat f ;
        NSInteger i ;
    };
    
    @interface TestClass : NSObject
    @property ( nonatomic ) union Test t ;
    @end
    
    @implementation TestClass
    
    -(void)setValue:(nullable id)value forUndefinedKey:(nonnull NSString *)key
    {
        Ivar v = class_getInstanceVariable( [ self class ], [ [ NSString stringWithFormat:@"_%@", key ] UTF8String ] ) ;
        if ( v )
        {
            char const * const encoding = ivar_getTypeEncoding( v ) ;
            if ( encoding[0] == '(' ) // unions only
            {
                size_t size = 0 ;
                NSGetSizeAndAlignment( encoding, &size, NULL ) ;
    
                uintptr_t ptr = (uintptr_t)self + ivar_getOffset( v ) ;
                [ (NSValue*)value getValue:(void*)ptr ] ;
    
                return ;
            }
        }
    
        objc_property_t prop = class_getProperty( [ self class ], [ key UTF8String ] ) ;
        if ( prop )
        {
            char const * const encoding = property_copyAttributeValue( prop, "T" ) ;
            if ( encoding[0] == '(' )   // unions only
            {
                objc_setAssociatedObject( self, NSSelectorFromString( key ), value, OBJC_ASSOCIATION_COPY ) ;
                return ;
            }
        }
    
        [ super setValue:value forUndefinedKey:key ] ;
    }
    
    -(nullable id)valueForUndefinedKey:(nonnull NSString *)key
    {
        Ivar v = class_getInstanceVariable( [ self class ], [ [ NSString stringWithFormat:@"_%@", key ] UTF8String ] ) ;
        if ( v )
        {
            char const * const encoding = ivar_getTypeEncoding( v ) ;
            if ( encoding[0] == '(' )
            {
                size_t size = 0 ;
                NSGetSizeAndAlignment( encoding, &size, NULL ) ;
    
                uintptr_t ptr = (uintptr_t)self + ivar_getOffset( v ) ;
                NSValue * result = [ NSValue valueWithBytes:(void*)ptr objCType:encoding ] ;
                return result ;
            }
        }
    
        objc_property_t prop = class_getProperty( [ self class ], [ key UTF8String ] ) ;
        if ( prop )
        {
            return objc_getAssociatedObject( self, NSSelectorFromString( key ) ) ;
        }
    
        return [ super valueForUndefinedKey:key ] ;
    }
    
    @end
    
    
    int main(int argc, const char * argv[])
    {
        @autoreleasepool
        {
            union Test u0 = { .i = 1234 } ;
            TestClass * const testClass = [ TestClass new ] ;
    
            [ testClass setValue:[ NSValue valueWithBytes:&u0 objCType:@encode( typeof( u0 ) ) ] forKey:@"t" ] ;
            assert( testClass.t.i == 1234 ) ;
    
            NSValue * const result = [ testClass valueForKey:@"t" ] ;
    
            union Test u1 ;
            [ result getValue:&u1 ] ;
    
            assert( u1.i == 1234 ) ;
        }
        return 0;
    }
    

    (Paste it into your main.m file to try)