6

I'm trying to get a list of all the properties of an unknown class and the class of every property. By the moment I get a list of all the properties of an object(I do it recursively to get all of the superclasses). I inspired in this post

+ (NSArray *)classPropsFor:(Class)klass
{    
    NSLog(@"Properties for class:%@", klass);
    if (klass == NULL || klass == [NSObject class]) {
        return nil;
    }

    NSMutableArray *results = [[NSMutableArray alloc] init];

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            [results addObject:propertyName];
        }
        NSArray* dict = [self classPropsFor:[klass superclass]];
        [results addObjectsFromArray:dict];
    }
    free(properties);

    return [NSArray arrayWithArray:results];
}

So now I want the class of every property and I do:

NSArray* properties = [PropertyUtil classPropsFor:[self class]];
for (NSString* property in properties) {
    id value= [self valueForKey:property];
    NSLog(@"Value class for key: %@ is %@", property, [value class]);
}

The problem is it works for NSStrings or but not for custom classes, for that it returns me null. I want to recursively create a dictionary that represents an object that can have other objects inside and as I thinks I need to know the class of every property, is that possible?

Community
  • 1
  • 1
Jpellat
  • 907
  • 7
  • 29

4 Answers4

7

Just made a tiny method for this.

// Simple as.
Class propertyClass = [customObject classOfPropertyNamed:propertyName];

Could be optimized in many ways, but I love it.


Implementation goes like:

-(Class)classOfPropertyNamed:(NSString*) propertyName
{
    // Get Class of property to be populated.
    Class propertyClass = nil;
    objc_property_t property = class_getProperty([self class], [propertyName UTF8String]);
    NSString *propertyAttributes = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
    NSArray *splitPropertyAttributes = [propertyAttributes componentsSeparatedByString:@","];
    if (splitPropertyAttributes.count > 0)
    {
        // xcdoc://ios//library/prerelease/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
        NSString *encodeType = splitPropertyAttributes[0];
        NSArray *splitEncodeType = [encodeType componentsSeparatedByString:@"\""];
        NSString *className = splitEncodeType[1];
        propertyClass = NSClassFromString(className);
    }
    return propertyClass;
}

It is part of eppz!kit, within a developing object representer called NSObject+EPPZRepresentable.h. It actually does what you are to achieve originally.

// Works vica-versa.
NSDictionary *representation = [customObject dictionaryRepresentation];
CustomClass = [CustomClass representableWithDictionaryRepresentation:representation];

It encodes many types, iterate trough collections, represents CoreGraphics types, UIColors, also represent / reconstruct object references.


New version spits you back even C type names and named struct types as well:

NSLog(@"%@", [self typeOfPropertyNamed:@"index"]); // unsigned int
NSLog(@"%@", [self typeOfPropertyNamed:@"area"]); // CGRect
NSLog(@"%@", [self typeOfPropertyNamed:@"keyColor"]); // UIColor

Part of eppz!model, feel free to use method implementations at https://github.com/eppz/eppz.model/blob/master/eppz!model/NSObject%2BEPPZModel_inspecting.m#L111

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
2

UPDATED

This doesn't work for values that are nil. Instead you should use the runtime C API to obtain the class from the corresponding ivar or accessor method.

jlehr
  • 15,557
  • 5
  • 43
  • 45
  • If the property is `nil`, how will this yield the class? – jlehr May 21 '12 at 15:04
  • Thanks! Well see for the nil values :) – Jpellat May 21 '12 at 15:09
  • You're welcome. Another consideration is that invoking accessor methods might have side-effects -- for example, lazy initialization -- that you might not want to trigger at this point. – jlehr May 21 '12 at 15:13
  • I like this answer the best, worst case you can use KVC and offload the value to an `id fooBar` and the just ask for `fooBar`'s class. If you get `nil` back then you know you don't have the right key path. – Dean Kelly Oct 26 '14 at 04:31
2

You should probably store the class (as a string) for each property at the same time as you store the propertyName. Maybe as a dictionary with property name as the key and class name as the value, or vice versa.

To get the class name, you can do something like this (put this right after you declare propertyName):

NSString* propertyAttributes = [NSString stringWithUTF8String:property_getAttributes(property)];
NSArray* splitPropertyAttributes = [propertyAttributes componentsSeparatedByString:@"\""];
if ([splitPropertyAttributes count] >= 2)
{
    NSLog(@"Class of property: %@", [splitPropertyAttributes objectAtIndex:1]);
}

The string handling code is because the attributes include a number of pieces of information - the exact details are specified here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html

Rob B
  • 1,485
  • 12
  • 16
0

The following added to an NSObject category does the trick.

- (Class) classForKeyPath:(NSString*)keyPath {
    Class class = 0;

    unsigned int n = 0;
    objc_property_t* properties = class_copyPropertyList(self.class, &n);
    for (unsigned int i=0; i<n; i++) {
        objc_property_t* property = properties + i;
        NSString* name = [NSString stringWithCString:property_getName(*property) encoding:NSUTF8StringEncoding];
        if (![keyPath isEqualToString:name]) continue;

        const char* attributes = property_getAttributes(*property);
        if (attributes[1] == '@') {
            NSMutableString* className = [NSMutableString new];
            for (int j=3; attributes[j] && attributes[j]!='"'; j++)
                [className appendFormat:@"%c", attributes[j]];
            class = NSClassFromString(className);
        }
        break;
    }
    free(properties);

    return class;
}
aepryus
  • 4,715
  • 5
  • 28
  • 41