In Objective-C Determine if a Property is an int, float, double, NSString, NSDate, NSNumber, etc
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 perform these comparisons in Objective-C):
id object = [[[开发者_StackOverflowRecord alloc] init] autorelease];
NSString *element = @"date";
NSString *data = @"2010-10-16";
objc_property_t property = class_getProperty([object class], [element UTF8String]);
const char *attributes = property_getAttributes(property);
char buffer[strlen(attributes) + 1];
strcpy(buffer, attributes);
char *attribute = strtok(buffer, ",");
if (*attribute == 'T') attribute++; else attribute = NULL;
if (attribute == NULL);
else if (strcmp(attribute, "@\"NSDate\"") == 0) [object setValue:[NSDate convertToDate:self.value] forKey:element];
else if (strcmp(attribute, "@\"NSString\"") == 0) [object setValue:[NSString convertToString:self.value] forKey:element];
else if (strcmp(attribute, "@\"NSNumber\"") == 0) [object setValue:[NSNumber convertToNumber:self.value] forKey:element];
I have looked through the class_getProperty and property_getAttributes, however I am still not sure how to do the above comparisons.
@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 anNSNumber
orNSValue
(as appropriate). If it comes back as anNSNumber
, you can then easily extract a double representation (doubleValue
) and use that as anNSTimeInterval
to create anNSDate
. I would probably recommend this approach.Special case each type.
property_getAttributes()
returns achar*
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 outNSPropertyDescription
.
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.
[object isKindOfClass:[NSDate class]]
, etc.
精彩评论