简体   繁体   中英

Dynamically setting unknown readonly property at runtime in Objective-C

I'm building a class that sets properties of a subclass dynamically at runtime from a plist, that works like this:

Example

属性列表编辑器屏幕截图

You declare your properties in a subclass to match the names of keys:

#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

That's it! The values are automatically populated at runtime without any additional code:

[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);

}];

Problem

I set the properties at runtime by generating a selector and calling it with the value I want to set like this:

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

But, if for some reason a property is readonly the selector won't exist, so I'm looking for an alternative. I've found a way to identify that a property is readonly here:

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

Perhaps if there's a way to set a objc_property_t ref directly.

Update

Through comments, I've realized there's some confusion. I think the core of my question is whether or not its possible to set an unknown property at runtime another way besides calling the selector like I'm doing.

Resources

Full Project: Here!

CodeReview Post that prompted this question: Here!

SetValue: forKey: Update

I have a readonly property:

@property (readonly) NSString * readOnlyProperty;

I declare this in the class:

+ (BOOL) accessInstanceVariablesDirectly {
    return YES;
}

I call this:

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

// -- OR -- // 

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

Either way, value is still null.

UPDATE

You can try using setValue:forKey: for a readonly property, if the target class has defines the class method accessInstanceVariablesDirectly to return YES and if the property stores its value in a conventionally-named instance variable. See “ Default Search Pattern for setValue:forKey:” in the Key-Value Coding Programming Guide . KVC will unbox a primitive value if necessary.

ORIGINAL

It is not possible to set a property except by calling the property's setter, because a property is defined as a getter and optionally a setter. You can use the setValue:forKey: method of Key-Value Coding (KVC), and that's simpler and more reliably than constructing the setter name yourself, but under the covers that still calls the property's setter.

It is possible to set an instance variable using the Objective-C runtime. Look at the class_getInstanceVariable , object_setInstanceVariable , and object_setIvar methods .

You can guess that a property's value is stored in an instance variable whose name is the property named with an _ prefix. However, this is only a convention. The compiler uses the convention for auto-synthesized properties, but the compiler does not enforce the convention.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM