I need to determine an object\'s property (passed by name) type in order to perform deserialization from XML. I have some general pseudo-code (however I am unsure of how to
[object isKindOfClass:[NSDate class]]
, etc.
@Ahruman's answer is correct, if you're dealing with objects. Let me suggest some alternatives:
valueForKey:
: If you use [myObject valueForKey:@"myPropertyName"]
, it will return an object. If the property corresponds to some sort of primitive (int
, float
, CGRect
, etc), then it will be boxed for you into an NSNumber
or NSValue
(as appropriate). If it comes back as an NSNumber
, you can then easily extract a double representation (doubleValue
) and use that as an NSTimeInterval
to create an NSDate
. I would probably recommend this approach.Special case each type. property_getAttributes()
returns a char*
representing all of the attributes of the property, and you can extract the type by doing this:
const char * type = property_getAttributes(class_getProperty([self class], "myPropertyName")); NSString * typeString = [NSString stringWithUTF8String:type]; NSArray * attributes = [typeString componentsSeparatedByString:@","]; 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 ....
This is pedantically more correct than Louis's answer, because while most types have a single-character encoding, they don't have to. (his suggestion assumes a single-character encoding)
Finally, if you're doing this on a subclass of NSManagedObject
, then I would encourage checking out NSPropertyDescription
.
From these alternatives, you can probably see that letting the runtime box the value for you is probably simplest.
edit extracting the type:
From the code above, you can extract the class name like so:
if ([typeAttribute hasPrefix:@"T@"] && [typeAttribute length] > 1) {
NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns @"NSDate" into NSDate
Class typeClass = NSClassFromString(typeClassName);
if (typeClass != nil) {
[object setValue:[self convertValue:self.value toType:typeClass] forKey:element];
}
}
And then instead of using class method categories to do the conversion (ie, [NSDate convertToDate:]
), make a method on self
that does that for you and accepts the desired type as a parameter. You could (for now) do it like:
- (id) convertValue:(id)value toType:(Class)type {
if (type == [NSDate class]) {
return [NSDate convertToDate:value];
} else if (type == [NSString class]) {
return [NSString convertToString:value];
} ...
}
Part of me is wondering, though: why on earth are you needing to do things this way? What are you making?
If you only care about classes you can use isKindOfClass:
, but if you want to deal with scalars you are correct that you need to use property_getAttributes()
, it returns a string that encodes the type information. Below is a basic function that demonstrates what you need to do. For examples of encoding strings, look here.
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([object class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
char *property_name = property_getName(property);
char *property_type = property_getAttributes(property);
switch(property_type[1]) {
case 'f' : //float
break;
case 's' : //short
break;
case '@' : //ObjC object
//Handle different clases in here
break;
}
}
Obvviously you will need to add all the types and classes you need to handle to this, it uses the normal ObjC @encode types.