Reputation: 53112
I'm building a class that sets properties of a subclass dynamically at runtime from a plist, that works like this:
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);
}];
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.
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.
Full Project: Here!
CodeReview Post that prompted this question: Here!
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.
Upvotes: 0
Views: 1627
Reputation: 385500
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.
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.
Upvotes: 3