How to get the value of a variable based on it's string name? - objective-c

I want to get the value of all ivars in an Objective-C class and load them into an array.
I have the following code:
Class class = [self class];
NSString *myClassName = NSStringFromClass([self class]);
unsigned int count;
Ivar *ivars = class_copyIvarList(class, &count);
for (int i=0; i<count; i++) {
Ivar ivar = ivars[i];
const char* name = ivar_getName(ivar);
const char* typeEncoding = ivar_getTypeEncoding(ivar);
NSLog(#"Class Name: %# ivar: %s Type Encoding: %s",myClassName, name, typeEncoding);
}
So I have the name as a string (name) and now I need the value of that ivar.
Something like:
value = [self valueFromStringName(name)];
So that if I have a date called myDate I can pass the string myDate into a method and it would return the date value it holds.
I've see routines that run methods from the string name of the method, but haven't see where you can get the value from a string name.

Given you are already using runtime functions why not continue and use:
id object_getIvar(id object, Ivar ivar)
to obtain the value of ivar?
If you really wish to use a string look up KVC, "Accessor Search Patterns for Simple Attributes", and valueForKey: on NSObject.
HTH

Related

How could I check for a particular property at runtime, along with its return type?

Since property named "age" would always have a selector named "age" as well, I could use respondsToSelector as this question suggests and that will tell me if a particular selector exists at runtime in any given object.
If a property named "age" exists, I can verify that. How could I know if that selector (the read method for that property) returns an object (id) or non-object (int)?
Is such type determination possible at runtime, or is the Objective-C way to always assume that someone implemented that method using the type I'm hoping it used, or can I also verify the return type?
This is using the latest Objective-C version (LLVM 4.1) in XCode 4.5.
Update: This is the utility-category-on-NSObject that I came up with:
- (NSString*) propertyType: (NSString*)propname
{
objc_property_t aproperty = class_getProperty([self class], [propname cStringUsingEncoding:NSASCIIStringEncoding] ); // how to get a specific one by name.
if (aproperty)
{
char * property_type_attribute = property_copyAttributeValue(aproperty, "T");
NSString *result = [NSString stringWithUTF8String:property_type_attribute];
free(property_type_attribute);
return result;
}
else
return nil;
}
While looking into this question I also wrote this handy-dandy utility method that
can list all the properties on this object:
- (NSArray*) properties;
{
NSMutableArray *results = [NSMutableArray array];
#autoreleasepool {
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char * aname=property_getName(property);
[results addObject:[NSString stringWithUTF8String:aname]];
//const char * attr= property_getAttributes(property);
//[results addObject:[NSString stringWithUTF8String:attr]];
}
if (properties) {
free(properties);
}
} // end of autorelease pool.
return results;
}
You could use class_copyPropertyList to get a list of properties declared in a class.
class_copyPropertyList
Describes the properties declared by a class.
And then property_getAttributes:
property_getAttributes
Returns the attribute string of an property.
Here you can find some more concrete hints and examples.
As a side note, the following statement:
Since property named "age" would always have a selector named "age" as well
is not correct, since a property can have custom getter and/or setter:
#property (nonatomic, getter=isImmediate) BOOL immediate;
EDIT:
Some sample code I found in another S.O. post:
const char * type = property_getAttributes(class_getProperty([self class], "myPropertyName"));
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString components separatedByString:#","];
NSString * typeAttribute = [attributes objectAtIndex:0];
NSString * propertyType = [typeAttribute substringFromIndex:1];
const char * rawPropertyType = [propertyType UTF8String];
if (strcmp(rawPropertyType, #encode(float)) == 0) {
//it's a float
} else if (strcmp(rawPropertyType, #encode(int)) == 0) {
//it's an int
} else if (strcmp(rawPropertyType, #encode(id)) == 0) {
//it's some sort of object
} else ....
One approach you can take, assuming you know the property name already, is to use the class_getProperty function. You can also use the property_copyAttributeValue() function to get just a particular attribute by name:
objc_property_t number_property = class_getProperty([MyClass class], "number");
char *number_property_type_attribute = property_copyAttributeValue(number_property, "T");
NSLog(#"number property type attribute = %s", number_property_type_attribute);
Will log:
2013-01-14 14:45:37.382 RuntimeFun[61304:c07] number property type
attribute = i
Assuming MyClass looks something like:
#interface MyClass : NSObject
#property (nonatomic) int number;
#end
#implementation MyClass
#end
One you have your type attribute string, you can then compare it to the various Objective-C type encodings. Once you're done with your comparison, be sure to call free() on your attribute string.

Convert variable name to string

How can I convert a variable name into a string?
Example:
From this:
NSString *someVariable
int otherVariable
I want to get a NSString with the actual name of the variable, no matter what type it is.
So, for the two variables above I would want to get their names (someVariable, otherVariable).
I managed to solve my problem with this code snippet:
Import the objc runtime
#import <objc/runtime.h>
and you can enumerate the properties with:
- (NSArray *)allProperties
{
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++)
{
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
free(properties);
return rv;
}
Hope it helps someone.
Just add " ... " around the variable name. i.e.
"someVariable"
"otherVariable"
to get the string (as a const char*.) If you want an NSString*, use
#"someVariable"
#"otherVariable"
Within a macro, you can use the construction #... to put the quote ... unquote around a macro variable, e.g.
#define MyLog(var) NSLog(#"%s=%#", #var, var)
so that
MyLog(foo);
is expanded to
NSLog(#"%s=%#", "foo", foo);
These are C declarations, and C does not have the introspection capability to give you what you want.
You could probably write a preprocessor macro that would both declare a variable and also declare and initialize a second variable with the name of the first.
But this begs the question of why you need this level of introspection at all.

Objective-C--Parse NSString to a callable method

Could I parse an NSString to have it call a method that matches its name in Objective-C? Here is an example:
If I had a string called doSomething, and I had a method called -(void) doSomething, could I do something like scanf to parse whatever text I typed in to check if there were any method matching it, and if yes then call that method?
Try NSSelectorFromString(#"methodName"); and the associated NSObject methods like respondsToSelector:
jxpx777's answer will give you the information you were looking for, but in case you want more, the runtime has a long list of C functions that provide a fairly complete introspection of objects and classes.
For instance, if you want an NSArray of method names implemented by a class, you can do something like this:
Class myClass = [self class];
unsigned int methodCount;
Method *methods = class_copyMethodList(myClass, &methodCount);
NSMutableArray *methodNames = [NSMutableArray arrayWithCapacity:10];
for (int i = 0; i < methodCount; i++) {
const char *methodNameCStr = sel_getName(method_getName(methods[i]));
NSString *methName = [NSString stringWithCString:methodNameCStr
encoding:NSASCIIStringEncoding];
[methodNames addObject:methName];
}
free(methods);
NSLog(#"Methods: %#", methodNames);
You will notice that plain C calls and Objective-C/Cocoa are mixed freely.

Get property name as a string

I need a way to pass a property and get the name assigned to it. Any suggestions?
#property (nonatomic, retain) MyObject *crazyObject;
NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return #"crazyObject"
You can try this:
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([self class], &propertyCount);
NSMutableArray * propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
NSLog(#"Names: %#", propertyNames);
It's as simple as this...expanding upon what Chuck already mentioned:
#ifndef STR_PROP
#define STR_PROP( prop ) NSStringFromSelector(#selector(prop))
#endif
You then use it like so:
NSString *strProp = STR_PROP(myProperty);
Background
Keep in mind that properties are really just, to quote Apple, "a syntactical shorthand for declaring a class’s accessor methods." In fact, by itself, the #property declaration doesn't even work. Your #synthesize statement translates the #property into the equivalent of two methods:
- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;
Which one is used depends on the context surrounding your self.crazyObject. (#synthesize also creates a matching instance variable if you didn't do it yourself.) The offshoot of all this is that you can't really translate to and from a property with one single method.
Proposed Solution
You can use what Apple already provides:
NSString *foo = NSStringFromSelector(#selector(myClassProperty));
Or do something custom:
Given that self.crazyObject really translates to either [self crazyObject] or [self setCrazyObject:foo] by the time your code is running, ou'll probably need two methods, like:
- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;
You might then want at least 2 companion methods such as:
- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;
Within these methods, you can use the Foundation functions NSStringFromSelector and NSSelectorFromString to convert back and forth between SEL and NSString. Use whatever string manipulations you like to convert back and forth between your setter string (setCrazyObject) and your property name (crazyObject).
A complete solution is hard to provide without knowing the exact use case, but hopefully this provides some more clues for anyone trying to accomplish something similar. There might even be some useful things made possible by combining this approach with Oscar's answer.
Here is a function that returns the name of an ivar, so basically it not only returns the properties but any ivar of the class. I haven't found a way to get the property directly so I used the ivar trick.
#import <objc/objc.h>
/// -----
- (NSString *)nameOfIvar:(id)ivarPtr
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
id pointer = object_getIvar(self, ivar);
if(pointer == ivarPtr)
{
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
break;
}
}
free(ivars);
}
return name;
}
After searching and debugging i find solution for me...
Added #import <objc/runtime.h>
Methods object_getIvar(id obj, Ivar ivar) send bad access and app crashes. i modify some code and it worked great:
+(NSString*)stringWithProperty:(id)property withClass:(id)controller
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([controller class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([controller valueForKey:name] == property)
{
break;
}
}
free(ivars);
}
return name;
}
Modifying the solution, it works when your object is allocated already, otherwise it returns nil:-
NSString * NSStringFromProperty(NSObject* property, NSObject* class)
{
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([class class], &propertyCount);
NSString *name = nil;
for (unsigned int i = 0; i < propertyCount; ++i)
{
name = [NSString stringWithUTF8String:property_getName(properties[i])];
NSObject *object = [class valueForKey:name];
if (object != nil && object == property)
{
break;
}
else
{
name = nil;
}
}
free(properties);
return name;
}
You can use
NSString *str = NSStringFromSelector(#selector(crazyObject));
The good thing about this approach is that:
Xcode will autocomplete word crazyObject for you.
When later on you will change the property name from crazyObject to myCrazyObject, Xcode will add a warning saying "unrecognized selector!" -- pretty good for debugging.
I use this method so often, that I even created a function, which allows to write less letters:
NSString * __nonnull sfs(SEL __nonnull theSelector)
{
if (!theSelector)
{
abort();
}
return NSStringFromSelector(theSelector);
}
Now your final solution can look like this:
NSString *str = sfs(#selector(crazyObject));
From Get property name as string, without using the runtime reference library, just define:
#define propertyKeyPath(property) (#""#property)
#define propertyKeyPathLastComponent(property) [[(#""#property) componentsSeparatedByString:#"."] lastObject]
And then you can do something like this:
NSLog(#"%#", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street
You may check my approach at Gist to get the string for a property with autocompletion and compile-time check.
How to use:
Get the property name for a class:
#interface AnyClass : NSObject
#property (strong) NSData *data;
#end
// == My approach ==
// C string for a class
PropertyNameForClass(AnyClass, data); // ==> "data"
// NSString for a class
PropertyStringForClass(AnyClass, data); // ==> #"data"
// Bad approach (no autocompletion; no compile-time check):
NSString *propertyName = #"data";
Get the property name for a protocol:
#protocol AnyProtocol
#property (strong) NSDate *date;
#end
// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date); // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date); // ==> #"date"
Unconventional, hacky, ugly, late, but... as strong-named as it gets and works like a charm:
#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:#"."] lastObject] componentsSeparatedByString:#" "] lastObject] stringByReplacingOccurrencesOfString:#"]" withString:#""] : #""
Sample usage:
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname)); // surname
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate])); // birthDate
...

Attribute introspection to get a variable in Objective-C

Given a variable id x and a string NSString *s how can I get the instance attribute with name s for variable x?
ie. If we write NSString *s=#"a", then we want x.a
The Objective-C Runtime Reference lists
Ivar class_getInstanceVariable(Class cls, const char * name)
which returns an opaque type representing an instance variable in a class. You then pass that to
id object_getIvar(id object, Ivar ivar)
to get the actual instance variable. So you could say
#import <objc/runtime.h>
id getInstanceVariable(id x, NSString * s)
{
Ivar ivar = class_getInstanceVariable([x class], [s UTF8String]);
return object_getIvar(x, ivar);
}
if the instance variable is an object. However, if the instance variable is not an object, call
Ivar object_getInstanceVariable(id obj, const char * name, void ** outValue)
passing in a pointer to a variable of the right type. For example, if the instance variable is an int,
int num;
object_getInstanceVariable(x, [s UTF8String], (void**)&num);
will set num to the value of the integer instance variable.
Providing that x is key-value coding compliant for the a property, you can just do this:
id result = [x valueForKey:s]