【问题标题】:iOS - Runtime Dynamic Class not key-value coding compliantiOS - 运行时动态类不符合键值编码
【发布时间】:2016-04-22 21:37:07
【问题描述】:

我一直在加快使用 Objective-C 在运行时创建动态类的速度,并且遇到了一个我似乎无法解决的问题。找了好久,还是一头雾水。

这里的问题是:

[runtimeClassInstance setValue:age forKey:@"age"];

调用实例化的对象:

id runtimeClassInstance = [[NSClassFromString(@"Employee") alloc] init];

创建者

[NewClassMaker buildClassFromDictionary:@[@"FirstName", @"LastName",\
                                          @"Age", @"Salary"]
                               withName:@"Employee"];

这是一个 NSUnknownKeyException:this class is not key value coding-compliant for key age 错误。

如果我使用以下调用方法,一切正常:

SEL setAgeSelector = NSSelectorFromString(@"setAge");
NSInvocation *call = [NSInvocation invocationWithMethodSignature:[[NSClassFromString(@"Employee") class] instanceMethodSignatureForSelector:setAgeSelector]];
call.target = runtimeClassInstance;
call.selector = setAgeSelector;
NSNumber *age = @(25);
[call setArgument:&age atIndex:2];
[call invoke];

现在创建类的静态方法是这样的:

+(NSDictionary*)buildClassFromDictionary:(NSArray*)propNames withName:(NSString*)className
{
    NSMutableDictionary* keys = [[NSMutableDictionary alloc]init];
    Class newClass = NSClassFromString(className);

    if(newClass == nil)
    {
        newClass = objc_allocateClassPair([NSObject class], [className UTF8String], 0);

        // For each property name, add a new iVar, getter, and setter method for it.
        for(NSString* key in propNames)
        {
            NSString* propName = [self propName: key];
            NSString* iVarName = [self ivarName:propName];

            class_addIvar(newClass, [iVarName UTF8String] , sizeof(NSObject*), log2(sizeof(NSObject*)), @encode(NSObject));

            objc_property_attribute_t a1 = { "T", "@\"NSObject\"" };
            objc_property_attribute_t a2 = { "&", "" };
            objc_property_attribute_t a3 = { "N", "" };
            objc_property_attribute_t a4 = { "V", [iVarName UTF8String] };
            objc_property_attribute_t attrs[] = { a1, a2, a3, a4};

            class_addProperty(newClass, [propName UTF8String], attrs, 4);
            class_addMethod(newClass, NSSelectorFromString(propName), (IMP)getter, "@@:");
            class_addMethod(newClass, NSSelectorFromString([self setterName:propName]), (IMP)setter, "v@:@");

            [keys setValue:key forKey:propName];
        }

        Class metaClass = object_getClass(newClass);
        // This method is returning NO on purpose to find out why there is no key.
        class_addMethod(metaClass, @selector(accessInstanceVariablesDirectly), (IMP)accessInstanceVariablesDirectly, "B@:");

        // Auxilliary methods added to the new class instance so the accessor dynamic methods above can work.
        // Not sure if the initial impl of this class maker class worked.
        class_addMethod(newClass, @selector(ivarName:), (IMP)ivarNameForString, "@@:@");
        class_addMethod(newClass, @selector(propName:), (IMP)propNameForString, "@@:@");
        class_addMethod(newClass, @selector(propNameFromSetterName:), (IMP)propNameFromSetterNameString, "@@:@");

        // Add a customized description dynamic method to this class. It will dump out any list of properties added
        // to the object during init here.
        Method description = class_getInstanceMethod([NSObject class],
                                                     @selector(description));
        const char *types = method_getTypeEncoding(description);

        // now add
        class_addMethod(newClass, @selector(description), (IMP)Description, types);
        objc_registerClassPair(newClass);
    }
    return keys;
}

我正在考虑三种可能性:

  1. 我在这里错过了获取这些属性和 他们的动态访问器以符合键值的方式工作
  2. 如果我确实允许直接 iVar 访问,是否保留该值 发生了什么?
  3. NSInvocation 方法是最好的方法吗?

【问题讨论】:

  • setter方法的名称是setAge还是setAge:
  • NSInvocation 调用实际上期望调用没有尾随冒号的 'setAge' setter 方法。正是对 [setArgument:atIndex:] 的后续调用捕获了该值并正确组装了 setter 签名。
  • @MartinStoufer 您介意分享该问题的完整源代码吗?我很难通过同样的异常。还有什么是您调用的 propName 和 ivarName 函数,它们的作用是什么?还有几个你正在调用的函数,我不知道它们是做什么的。

标签: objective-c dynamic key-value-coding


【解决方案1】:

setter 方法名为setAgesetValue:forKey: 使用冒号搜索 setAge:。在 setter 方法的名称末尾添加一个冒号。在 Objective-C 中,冒号是方法名的一部分。

【讨论】:

  • 快到了。添加一个 kvo 观察者并将密钥更改为 setAge: 仍然会导致 *** 由于未捕获的异常 'NSUnknownKeyException' 而终止应用程序,原因:'[ setValue:forUndefinedKey:]: 这个类不是键值编码兼容键 setAge:.' 被抛出。
  • 把setter方法的名字改成setAge:,关键是AgeAccessor Search Patterns for Simple Attributes
  • 是的,还是不行。我认为 KVO 的幕后子类化正在破坏这个运行时类的某些东西。我用这个不同的键值测试了一些组合,它们都以类似的方式崩溃。我会整理一下并在此处发布指向 Git 项目的链接。欢迎大家检查它,看看他们发现了什么。
  • 哦,终于! @Willeke 的建议虽然不是最终答案,但确实让我回到了正确的轨道上。 KVO 键只是age,但我需要修改setter 方法以包含冒号。一旦我弄清楚了这一点并删除了一些明显无效的代码(KVO 观察者方法不再按照 Apple 文档的要求调用它的超级实现)。我已经检查了我的更改。我希望每个人都能从这个例子中得到一些启发。随时欢迎改进和建议
猜你喜欢
  • 2014-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多