Dynamically setting unknown readonly property at runtime in Objective-C - 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.

Related

Key-Value coding with auto-synthesized properties on NSObject subclass: respondsToSelector returns NO for all property accessors?

I have a simple NSObject subclass with some properties
#interface MyThing : NSObject
#property (nonatomic, copy) NSString *aString;
//... and so on
#end
But when I try to use key/value coding to set my properties via a dictionary:
+ (instancetype)thingFromDictionary:(NSDictionary *)dict
{
MyThing *newThing = [MyThing new];
for (NSString *key in dict)
{
if ([newThing respondsToSelector:#selector(key)])
{
//do stuff
[newThing setValue:[dict objectForKey:key] forKey:key];
}
else
{
NSLog(#"key %# doesnt exist. value %#", key, [dict objectForKey:key]);
}
}
return newThing;
}
It turns out that though the dictionary contains keys that match the exact names of my properties, respondsToSelector: always returns for NO for those keys. How do I ensure all properties are accessible via the key/value methods?
if ([newThing respondsToSelector:#selector(key)])
checks if the object responds to the the selector "key". The argument of #selector
is a literal key and not expanded.
To create a selector from a string variable, use NSSelectorFromString():
if ([newThing respondsToSelector:NSSelectorFromString(key)])
But note, as Gerd K correctly stated in a comment, this checks for the existence of a getter method for the property with that name. To check if the property can bet set
you have to check for the setter method, (e.g. setAString:):
NSString *key = #"aString";
NSString *setter = [NSString stringWithFormat:#"set%#%#:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]];
if ([newThing respondsToSelector:NSSelectorFromString(setter)])
...

How to get the property name from default setter name?

I am trying to figure out the original property name from the setter selector. For example I know that the setter is called setFoo: and would like to get foo. It should be a quite easy string processing task (remove set and change the first letter to lowercase) but I was wondering if there are any out of the box solution somewhere in the Objective-C runtime.
I would like to use it like this:
#interface MyClass : NSObject
#property (nonatomic, assign) BOOL foo;
#end
#implementation MyClass
#dynamic foo;
+(BOOL)resolveInstanceMethod:(SEL)sel
{
const char* selectorName = sel_getName(sel);
objc_property_t getterProperty = class_getProperty([self class], selectorName);
objc_property_t setterProperty = class_getProperty([self class], getPropertyNameFromSetterName(selectorName));
if (getterProperty) {
// now I know that the property was declared and I should provide
// the getter implementation
} else if (setterProperty) {
// I should provide the setter implementation
}
}
#end

iOS - NSMutableArray shows objects out of bounds on setting property

I have implemented the following code to assign NSMutableArray to a property -
NSMutableArray * anArray = [responseDictionary valueForKeyPath:#"tags"];
NSLog(#"The array length is=%d",[anArray count]);
for (NSString *s in anArray) {
NSLog(#"you are %#", s);
}
[self setActiveTagArray:anArray];
It prints out the string values fine. But in the setter function, if I place a breakpoint I see that it shows there are two objects but they are "Out of Scope". What does this mean? What am I doing wrong? My getter also does not fetch any values. The property functions -
-(void)setActiveTagArray:(NSMutableArray *)tags
{
activeTagArray = [[NSMutableArray alloc] init];
activeTagArray = tags;
//NSLog(#"%#",[activeTagArray count]);
}
-(NSMutableArray *)getActiveTagArray
{
return activeTagArray;
}
Is activeTagArray a class variable as well as a property. Consider using _activeTagArray as the class variable name. And then in the .m file just use #synthesize activeTagArray = _activeTagArray;, and for get the second two methods completely.
Response to comment:
You said "I have implemented the following code to assign NSMutableArray to a property". I took this to mean you have "#property (nonatomic, retain) NSMutableArray *activeTagArray;" in your .h file. If this is the case then you would access it thru otherObject'sNameForYourClassHere.activeTagArray.
#synthesize create accessors & mutators for you.

How can I add properties to an object at runtime?

Is it possible to add properties to an Objective C object at runtime?
It’s possible to add formal properties to a class via class_addProperty():
BOOL class_addProperty(Class cls,
const char *name,
const objc_property_attribute_t *attributes,
unsigned int attributeCount)
The first two parameters are self-explanatory. The third parameter is an array of property attributes, and each property attribute is a name-value pair which follow Objective-C type encodings for declared properties. Note that the documentation still mentions the comma-separated string for the encoding of property attributes. Each segment in the comma-separated string is represented by one objc_property_attribute_t instance. Furthermore, objc_property_attribute_t accepts class names besides the generic # type encoding of id.
Here’s a first draft of a program that dynamically adds a property called name to a class that already has an instance variable called _privateName:
#include <objc/runtime.h>
#import <Foundation/Foundation.h>
#interface SomeClass : NSObject {
NSString *_privateName;
}
#end
#implementation SomeClass
- (id)init {
self = [super init];
if (self) _privateName = #"Steve";
return self;
}
#end
NSString *nameGetter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
return object_getIvar(self, ivar);
}
void nameSetter(id self, SEL _cmd, NSString *newName) {
Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
id oldName = object_getIvar(self, ivar);
if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}
int main(void) {
#autoreleasepool {
objc_property_attribute_t type = { "T", "#\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t backingivar = { "V", "_privateName" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty([SomeClass class], "name", attrs, 3);
class_addMethod([SomeClass class], #selector(name), (IMP)nameGetter, "##:");
class_addMethod([SomeClass class], #selector(setName:), (IMP)nameSetter, "v#:#");
id o = [SomeClass new];
NSLog(#"%#", [o name]);
[o setName:#"Jobs"];
NSLog(#"%#", [o name]);
}
}
Its (trimmed) output:
Steve
Jobs
The getter and setter methods should be written more carefully but this should be enough as an example of how to dynamically add a formal property at runtime.
If you take a look at NSKeyValueCoding protocol, documented here, you can see that there is a message called:
- (id)valueForUndefinedKey:(NSString *)key
You should override that method to provide your custom result for the specified undefined property. Of course this assumes that your class uses the corresponding protocol.
This kind of approach is commonly uses to provide unknown behavior to classes (eg. a selector that doesn't exist).
#properties - no (i.e. using dot syntax etc). But you can add storage using using associated objects: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?.

Can I validate a #property value in Objective-C using #synthesized methods?

What it says on the tin: I'd like to use the #property/#synthesize syntax to define a property on my Objective-C 2.0 class, but I want to place restrictions on the range of values allowed in the property. For example:
#interface MyClass : NSObject {
int myValue;
}
#property (nonatomic) int myValue;
Implementation:
#implementation MyClass
#synthesize myValue(test='value >= 0');
Note that the syntax here is just an example. Is this, or something much like it possible? Alternately, what is the literal equivalent of a synthesized setter, so that I can ensure that I use the same object retention rules in my manual setters as is used in a synthesized one.
Assuming your properties are Key-Value compliant (as they would be if you are using #synthesize) you should also implement Key-Value compliant validators. Take a look at Apple's documentation on the matter: http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/Concepts/Validation.html
The important thing to note is that validation does not happen automatically except when using certain kinds of binding. You either call the validator directly or by calling validateValue:forKey:error:.
You could override the produced setter to call the validator before saving it but if you are using bindings this is probably not what you want to do as the validator will possibly be called more than once for a single modification.
Also note that the validator might change the value being validated.
So lets look at your example (untested, btw. I'm not near a Mac):
#implementation MyClass
#synthesize myValue;
-(BOOL)validateMyValue:(id *)ioValue error:(NSError **)outError
{
if (*ioValue == nil) {
// trap this in setNilValueForKey
// alternative might be to create new NSNumber with value 0 here
return YES;
}
if ( [*ioValue intValue] < 0 ) {
NSString *errorString = #"myValue must be greater than zero";
NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:errorString
forKey:NSLocalizedDescriptionKey];
NSError *error = [[[NSError alloc] initWithDomain:#"MyValueError"
code:0
userInfo:userInfoDict] autorelease];
*outError = error;
return NO;
} else {
return YES;
}
}
If you wanted to override the synthesised setter and make it do the validation (still untested):
- (void)setMyValue:(int)value {
id newValue = [NSNumber numberWithInt:value];
NSError *errorInfo = nil;
if ( [self validateMyValue:&newValue error:&errorInfo] ) {
myValue = [newValue intValue];
}
}
You can see we had to wrap the integer in an NSNumber instance to do this.
When you use the #synthesize the accessor methods are generated. You can implement your own which will overwrite the generated one.
You can put your own implementation inside the accessor methods, e.g. you can add value checking before assignment and so on.
You can ommit one or the other or both, the ones that you don't implement will be generated because of #synthesize, if you use #dynamic you are specifying that you will provide accessors either at compile or run time.
Accessors will have names derived from the property name myproperty and setMyproperty. The method signatures are standard so it is easy to implement your own. The actual implementation depends on property definition (copy, retain, assign) and if it is read-only or not (read-only doesn't get set accessor). For more details see objective-c reference.
Apple reference:
#synthesize You use the #synthesize
keyword to tell the compiler that it
should synthesize the setter and/or
getter methods for the property if you
do not supply them within the
#implementation block.
#interface MyClass : NSObject
{
NSString *value;
}
#property(copy, readwrite) NSString *value;
#end
#implementation MyClass
#synthesize value;
- (NSString *)value {
return value;
}
- (void)setValue:(NSString *)newValue {
if (newValue != value) {
value = [newValue copy];
}
}
#end