How to dynamically determine Objective-C property type? - objective-c

I'm trying to dynamically determine the type of a property in Objective-C. Based on what I have read on this site and elsewhere, I believe I am doing the right thing. However, my code isn't working.
The code snippet below demonstrates the problem. Attempting to get the property information for "backgroundColor" and "frame", both of which are valid properties of UIView, fails (class_getProperty() returns NULL):
id type = [UIView class];
objc_property_t backgroundColorProperty = class_getProperty(type, "backgroundColor");
fprintf(stdout, "backgroundColorProperty = %d\n", (int)backgroundColorProperty); // prints 0
objc_property_t frameProperty = class_getProperty(type, "frame");
fprintf(stdout, "frameProperty = %d\n", (int)frameProperty); // prints 0
Enumerating the properties as described here doesn't produce the expected results, either. The following code:
NSLog(#"Properties for %#", type);
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(type, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
generates this output:
2012-03-09 13:18:39.108 IOSTest[2921:f803] Properties for UIView
caretRect T{CGRect={CGPoint=ff}{CGSize=ff}},R,N,G_caretRect
gesturesEnabled Tc,N
deliversTouchesForGesturesToSuperview Tc,N
skipsSubviewEnumeration Tc,N
viewTraversalMark Tc,N
viewDelegate T#"UIViewController",N,G_viewDelegate,S_setViewDelegate:
inAnimatedVCTransition Tc,N,GisInAnimatedVCTransition
monitorsSubtree Tc,N,G_monitorsSubtree,S_setMonitorsSubtree:
backgroundColorSystemColorName T#"NSString",&,N,G_backgroundColorSystemColorName,S_setBackgroundColorSystemColorName:
userInteractionEnabled Tc,N,GisUserInteractionEnabled
tag Ti,N,V_tag
layer T#"CALayer",R,N,V_layer
Documented properties such as "backgroundColor", "frame", and others are missing, whereas undocumented properties like "caretRect" and "gesturesEnabled" are included.
Any help would be very much appreciated. In case it is relevant, I'm seeing this behavior on the iOS simulator. I don't know if the same thing would happen on an actual device.
Thanks,
Greg

You are getting the UIView properties, the problem is backgroundColor is not a UIView property, is a category property. Check UIView.h. I think you can't get a objc_category, but have a look at class-dump.

Dodging around the issue slightly, the following works:
NSMethodSignature *signature = [[UIView class]
instanceMethodSignatureForSelector:#selector(backgroundColor)];
NSLog(#"%s", [signature methodReturnType]);
So the runtime may somehow have lost the fact that backgroundColor is a property but you seem to start with that information anyway in the first code snippet so it just checks out the return type of the getter.

You can find category properties as methods.
#import ObjectiveC;
static void test(Class class, NSString* methodName) {
Method method = class_getInstanceMethod(class, NSSelectorFromString(methodName));
const char* type = method_copyReturnType(method);
printf("%s : %s\n", methodName.UTF8String, type);
free((void*)type);
}
Then you inspect some...
test([UILabel class], #"alpha");
test([UILabel class], #"textColor");
test([UILabel class], #"isHidden");
test([UILabel class], #"minimumScaleFactor");
After look at these defines in runtime.h
#define _C_ID '#'
#define _C_CLASS '#'
#define _C_SEL ':'
#define _C_CHR 'c'
#define _C_UCHR 'C'
#define _C_SHT 's'
#define _C_USHT 'S'
#define _C_INT 'i'
#define _C_UINT 'I'
...
Don't forget to respect getters/setters notation for BOOL properties, search 'isHidden' instead 'hidden'.

Related

Identifying properties in the header file vs. implementation file

I have been interested in using something along the the lines of the following code to
automate the building of my objects (since there are many of them with quite a few properties):
MyObject *myObject = [[myObject alloc] init];
unsigned int numberOfProperties = 0;
objc_property_t *propertyArray = class_copyPropertyList([MyObject class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++)
{
objc_property_t property = propertyArray[i];
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
if (propertyName)
{
id valueForProperty = [myObject valueForKey:propertyName];
[myObject setValue:valueForProperty forKey:propertyName];
}
}
free(propertyArray);
However, what I've noticed is that this code will try to run not just on the properties in my header file, but also all of my implementation properties as well, which I do not want.
Since Objective-C doesn't actually distinguish public vs private properties, I am not sure how to do this. Any thoughts on how to indicate that I'm only interested in the properties in the header file to simulate the same thing effectively?
In short, you don't. This information is not available in the compiled program. You'd need to write a custom preprocessor to do this if you really wanted to.

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.

Automatically copy property values from one object to another of a different type but the same protocol (Objective-C)

I have two classes with the same set of properties, declared using the #property directive in a protocol, they both implement. Now I was wondering if it is possible to automatically populate an instance of the first class with the values from an instance of the second class (and vice-versa).
I would like this approach to be robust, so that if I change the of properties declared in the protocol there will be no need to add extra code in the copying methods.
Yes, given the exact context there could be various approaches to this problem.
One I can think of at the moment is to first get all the properties of source object then use setValue:value forKey:key to set the values on the target object.
Code to retrieve all custom properties:
-(NSSet *)propertyNames {
NSMutableSet *propNames = [NSMutableSet set];
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];
NSString *propertyName = [[[NSString alloc]
initWithCString:property_getName(property)] autorelease];
[propNames addObject:propertyName];
}
free(properties);
return propNames;
}
You may want to checkout the Key-Value Coding Programming Guide for more information.

Getting an array of properties for an object in Objective-C

Is it possible to get an array of all of an object's properties in Objective C? Basically, what I want to do is something like this:
- (void)save {
NSArray *propertyArray = [self propertyNames];
for (NSString *propertyName in propertyArray) {
[self doSomethingCoolWithValue:[self valueForKey:propertyName]];
}
}
Is this possible? It seems like it should be, but I can't figure out what method my propertyNames up there should be.
I did some more digging, and found what I wanted in the Objective-C Runtime Programming Guide. Here's how I've implemented the what I wanted to do in my original question, drawing heavily from Apple's sample code:
#import <Foundation/NSObjCRuntime.h>
#import <objc/runtime.h>
- (void)save {
id currentClass = [self class];
NSString *propertyName;
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(currentClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
propertyName = [NSString stringWithCString:property_getName(property)];
[self doSomethingCoolWithValue:[self valueForKey:propertyName]];
}
}
I hope this will help someone else looking for a way to access the names of an object's properties programatically.
dont forget
free(properties);
after the loop or you will get a leak. The apple documentation is clear:
An array of pointers of type objc_property_t describing the properties
declared by the class. Any properties declared by superclasses are not
included. The array contains *outCount pointers followed by a NULL
terminator. You must free the array with free().