【问题标题】:Reading union properties using KVC in Objective-C在 Objective-C 中使用 KVC 读取联合属性
【发布时间】:2015-09-09 12:00:16
【问题描述】:

更新:

我将问题归结为根本无法在下面看到的类上使用键值编码

#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

根据我的理解和经验,我应该能够将非 NSObject 作为 NSValue 提取出来,但是,我发现使用 KVC 语法无法访问这些项目(定义为联合):

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

同样,如果我尝试访问底层变量:

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

这两个都抛出异常,因为找不到密钥。我在这里错过了什么?

上一个问题

我编写了一些代码,允许我使用诸如“transformation.position”之类的字符串访问(有点)任意结构

由于某种原因,当我尝试从 NSObject 读取属性时,代码在第二次跳转时停止工作。这里是

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];
}

我已经尝试了所有可能的属性访问语法,包括 Property、property 和 _property。

这是自定义的 NSObject 声明

#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

另外,在第一个循环之后,我可以看到调试器说 CMTransformation* 正在填充 currrent_object,所以我不知道为什么我不能访问它的属性?

【问题讨论】:

  • current_object 是 CMTransformation 吗?你为什么要把它变成id 的类型而不是明确的?
  • 这意味着能够迭代任何嵌套的 NSObject 结构,而不仅仅是给出的具体示例。在 ARC 中,您甚至可以对类成员变量使用 KV 编码。
  • 你可以尝试实现 -valueForUndefinedKey: 来修补这种情况。 (或者可能 +resolveInstanceMethod:
  • @nielsbot 本来打算这样做,但是使用 ARC,我们仍然无法动态访问它以将其传回我的知识,因为我们不允许使用像 object_getInstanceVariable 这样的低级函数。我应该注意到,如果我们能找到一种方法来获取指向它的指针,就很容易检测到它是什么类型。
  • 假设您有关联的 ivar,则存在 ivar_getOffset。我想我可能有一个可以发布的工作示例代码......它几乎可以工作了。

标签: ios objective-c introspection key-value-coding


【解决方案1】:

您对实现 valueForUndefinedKey:setValue:forUndefinedKey: 满意吗?

如果是这样,这可行:

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;
}

(粘贴到你的main.m文件中试试)

【讨论】:

  • 你太棒了!我正要放弃并接受廉价的黑客攻击,但这太棒了!它基本上填补了 Apple 在实施过程中的任何漏洞,使其能够与 Unions 一起正常工作。我会将这些函数添加到我们的基类中。现在可以按预期工作: id transform = [node valueForKey:@"transformation"]; id pos = [变换 valueForKey:@"position"]; GLKVector3 向量; [pos getValue:&vect];
  • 谢谢。我实际上花了很多时间试图让较低级别的方法工作,但看起来 obj-c 运行时中某处的代码在联合类型上主动失败。幸运的是他们仍然装箱/拆箱联合类型,所以实现 valueForUndefinedKey: 工作。
  • 仅供参考,我刚刚将此作为错误提交给 Apple。 (错误:21526543)。希望它会在未来的版本中得到修复。
【解决方案2】:

我相当肯定 KVC 不适用于 C 联合。请参阅 Objective-C KVO doesn't work with C unions 中 bbum 的讨论。我知道这与 KVO 有关,但他说:

哦……好样的。带工会的 KVO 根本不起作用。运行时似乎根本无法识别该类有一个名为 vectorUnionValue 的键。

这可能是你的问题。您可能需要创建一些其他属性才能访问它们。也许是这样的:

@property(nonatomic) GLKVector3 positionValue;

- (NSValue *)positionValue {
    return [NSValue valueWithPointer:&_positionValue];
}

我不喜欢那个;并且可能有更好的方法,但我怀疑你需要这些方面的东西。您可能无法通过这种方式加入工会。


这是内置在 KVC 中的。如果您有一个具有transformation 对象属性的对象,并且该对象具有position 属性,则路径transformation.position 将返回它。没必要自己拆。

但是transformation必须是符合key-value编码的对象本身。有符合 KVC 的规则,您对变量的不规则随机命名可能会破坏这一点。 Cocoa 变量和属性应该是前导小写驼峰式。你永远不应该有一个领先的上限(这是一个类),你应该避免内部下划线。 KVC 使用此命名约定来允许它自动查找属性。例如,它可以使用它来知道foo 的setter 是setFoo:(不是大写)。

而且所有的中间层都必须是对象。例如,GLKVector3 不是一个对象,所以你不能使用 KVC 来查询它的子组件。您必须返回整个向量。不幸的是,您只能通过查阅头文件或文档(或根据经验对它有直觉)知道 GLKVector3 是否是一个对象with 通常是结构;这个是结构的联合)。

【讨论】:

  • 不知道可以在语法中使用句点,但出于我们的目的(此处未显示)我们需要迭代。我正在更新问题以反映我的问题的根本原因。
猜你喜欢
  • 2013-03-17
  • 2010-10-21
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 2011-04-27
  • 1970-01-01
  • 2013-05-22
  • 1970-01-01
相关资源
最近更新 更多