在Objective-C中在运行时动态设置未知的只读属性

时间:2014-05-02 18:56:27

标签: objective-c

我正在构建一个在运行时从plist动态设置子类属性的类,其工作方式如下:

示例

Property list editor screenshot

您在子类中声明属性以匹配键的名称:

#import "PlistModel.h"

@interface CustomModel : PlistModel

@property (strong, nonatomic) NSString * StringPropertyKey;
@property (strong, nonatomic) NSDate * DatePropertyKey;
@property (strong, nonatomic) NSArray * ArrayPropertyKey;
@property (strong, nonatomic) NSDictionary * DictionaryPropertyKey;

@property int IntPropertyKey;
@property BOOL BoolPropertyKey;
@property float FloatPropertyKey;

@end

那就是它!这些值在运行时自动填充,无需任何其他代码:

[CustomModel plistNamed:@"CustomModel" inBackgroundWithBlock:^(PlistModel *plistModel) {

    CustomModel * customModel = (CustomModel *)plistModel;

    NSLog(@"StringProperty: %@", customModel.StringPropertyKey);
    NSLog(@"DateProperty: %@", customModel.DatePropertyKey);
    NSLog(@"ArrayProperty: %@", customModel.ArrayPropertyKey);
    NSLog(@"DictionaryProperty: %@", customModel.DictionaryPropertyKey);
    NSLog(@"IntProperty: %i", customModel.IntPropertyKey);
    NSLog(@"BoolProperty: %@", customModel.BoolPropertyKey ? @"YES" : @"NO");
    NSLog(@"FloatProperty: %f", customModel.FloatPropertyKey);

}];

问题

我在运行时通过生成一个选择器并使用我想要设置的值调用它来设置属性:

SEL propertySetterSelector = NSSelectorFromString(@"set<#PropertyName#>:"); 
void (*func)(id, SEL, id) = (void *)imp;
func(self, propertySetterSelector, objectToSet);

但是,如果出于某种原因属性为readonly,则选择器不会存在,所以我正在寻找替代方案。我找到了一种方法来确定一个属性是只读的:

- (NSMutableArray *) getPropertyNames {

    // Prepare Package
    NSMutableArray * propertyNames = [NSMutableArray array];

    // Fetch Properties
    unsigned count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    // Parse Out Properties
    for (int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        const char * name = property_getName(property);
        // NSLog(@"Name: %s", name);
        const char * attributes = property_getAttributes(property);
        NSLog(@"Attributes: %s", attributes);
        NSString * attributeString = [NSString stringWithUTF8String:attributes];
        NSArray * attributesArray = [attributeString componentsSeparatedByString:@","];
        if ([attributesArray containsObject:@"R"]) {
            // is ReadOnly
            NSLog(@"%s is read only", name);

            // -- CAN I SET THE PROPERTY HERE? -- //
            // property = @"Set"; ?
        }
        // Add to our array
        [propertyNames addObject:[NSString stringWithUTF8String:name]];
    }

    // Free our properties
    free(properties);

    // Send it off
    return propertyNames;
}

也许如果有办法直接设置objc_property_t参考号。

更新

通过评论,我意识到存在一些困惑。我认为我的问题的核心是,除了像我一样调用选择器之外,是否可以在运行时以另一种方式设置未知属性。

资源

完整项目:Here!

提示此问题的CodeReview帖子:Here!

SetValue:forKey:Update

我有一个只读属性:

@property (readonly) NSString * readOnlyProperty;

我在课堂上宣布这一点:

+ (BOOL) accessInstanceVariablesDirectly {
    return YES;
}

我称之为:

[self setValue:@"HI" forKey:[NSString stringWithUTF8String:name]];

// -- OR -- // 

[self setValue:@"HI" forKey:[NSString stringWithFormat:@"_%@",[NSString stringWithUTF8String:name]]];

无论哪种方式,值仍为空。

1 个答案:

答案 0 :(得分:3)

更新

您可以尝试将setValue:forKey:用于readonly属性,如果目标类已定义类方法accessInstanceVariablesDirectly以返回YES和如果属性将其值存储在常规命名的实例变量中。见“ Default Search Pattern for setValue:forKey:” in the Key-Value Coding Programming Guide。如有必要,KVC将取消打包原始值。

ORIGINAL

除了通过调用属性的setter之外,无法设置属性,因为属性已定义作为getter和可选的setter。您可以使用键值编码(KVC)的setValue:forKey:方法,这比自己构建setter名称更简单,更可靠,但仍然会调用属性的setter

可以使用Objective-C运行时设置实例变量。查看class_getInstanceVariable, object_setInstanceVariable, and object_setIvar methods

您可以猜测属性的值存储在一个实例变量中,该变量的名称是以_前缀命名的属性。但是,这只是一个惯例。编译器使用自动合成属性的约定,但编译器不强制执行约定。