31

Is there a way to get an array of class properties of certain kind? For example if i have interface like this

@interface MyClass : NSObject
    @property (strong,nonatomic) UILabel *firstLabel;
    @property (strong,nonatomic) UILabel *secondLabel;        
@end

can i get the reference to those labels in implementation without knowing their name?

@implementation MyClass
    -(NSArray*)getListOfAllLabels
    {
            ?????
    }        
@end

I know i can do it easily with [NSArray arrayWithObjects:firstLabel,secondLabel,nil], but i would like to do it with some kind of class enumeration like for (UILabel* oneLabel in ???[self objects]???)

animal_chin
  • 6,610
  • 9
  • 37
  • 41
  • 7
    Don't name methods with the `get` prefix; that is limited to only a very specific use case and this isn't it. – bbum Aug 02 '12 at 14:05
  • 4
    On iOS, consider using an IBOutletCollection. This is pretty much what it exists for. http://www.bobmccune.com/2011/01/31/using-ios-4s-iboutletcollection/ – isaac Sep 05 '13 at 02:02

6 Answers6

79

So more precisely, you want dynamic, runtime observaion of the properties, if I got it correctly. Do something like this (implement this method on self, the class you want to introspect):

#import <objc/runtime.h>

- (NSArray *)allPropertyNames
{
    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;
}

- (void *)pointerOfIvarForPropertyNamed:(NSString *)name
{
    objc_property_t property = class_getProperty([self class], [name UTF8String]);

    const char *attr = property_getAttributes(property);
    const char *ivarName = strchr(attr, 'V') + 1;

    Ivar ivar = object_getInstanceVariable(self, ivarName, NULL);

    return (char *)self + ivar_getOffset(ivar);
}

Use it like this:

SomeType myProperty;
NSArray *properties = [self allPropertyNames];
NSString *firstPropertyName = [properties objectAtIndex:0];
void *propertyIvarAddress = [self getPointerOfIvarForPropertyNamed:firstPropertyName];
myProperty = *(SomeType *)propertyIvarAddress;

// Simpler alternative using KVC:
myProperty = [self valueForKey:firstPropertyName];

Hope this helps.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
  • this works really nice! Only, how can i access those properties after this? This gives me strings of names, but not actual pointers to objects... it must be really easy i guess :) im just not familiar with objc/runtime class :) Thanks! – animal_chin Aug 02 '12 at 09:41
  • 3
    Once you have the string it is probably easier just to use KVC and use `[self valueForKey:propertyName];`. It's normally better to use the accessors than using the backing ivar straight - so you can utilise uniform access principle and give your self some hooks to hang your hat on later on. – Paul.s Aug 02 '12 at 10:29
  • @Paul.s that's perfectly true, but what if those ivars are not KVC-compliant? –  Aug 02 '12 at 10:44
  • @H2CO3 In that case I think the dev would have bigger design issues to worry about. The Default Search Pattern for `valueForKey:` is pretty extensive - looking for method called `get`, ``, `is`, `countOf`, `objectInAtIndex:` followed by looking for some more methods and then finally trying to access the ivar straight looking for `_`, `_is`, ``, or `is`. I think if a dev is fighting the framework so much that this won't work then they are in phase 1 `unconscious incompetence` of the `conscious competence learning model` :) ...and need to read more – Paul.s Aug 02 '12 at 11:16
  • 2
    Don't name methods with the `get` prefix; that is limited to only a very specific use case and this isn't it. – bbum Aug 02 '12 at 14:06
  • How about including properties in super classes? – Jasper Blues Nov 10 '13 at 03:54
  • @JasperBlues What do you mean by "including a property"? –  Nov 10 '13 at 07:03
  • 1
    @H2CO3 - (hey buddy) I believe class_copyPropertyList only lists the properties for the current Class object, if you want to go up the inheritance chain, some recursion is required. . this is according to the docs, however I didn't test because I ended up using another approach in what I'm doing. – Jasper Blues Nov 10 '13 at 09:15
  • @JasperBlues Ah OK. That is right: [link](http://stackoverflow.com/questions/848636/objective-c-2-0-class-copypropertylist-how-to-list-properties-from-categorie). –  Nov 10 '13 at 09:19
  • 1
    A combination of allPropertyNames and [self valueForKey:propertyName], [self setValue:@"" forKey:propertyName] worked for me. Got error using pointerOfIvarForPropertyNamed under ARC. – LordParsley Aug 12 '15 at 12:12
  • Please look at my answer if you use ARC and you care about getting the properties of superclasses. – jlukanta May 20 '16 at 18:54
13

use attributeKeys method of NSObject.

    for (NSString *key in [self attributeKeys]) {

        id attribute = [self valueForKey:key];

        if([attribute isKindOfClass:[UILabel  class]])
        {
         //put attribute to your array
        }
    }
serhats
  • 246
  • 2
  • 7
  • 15
    this looks like the most elegant solution but sadly method attributeKeys is only available in Mac OS X, not in iOS... – animal_chin Aug 02 '12 at 12:19
9

Check out this link. It is an objective c wrapper over objective C runtime.

You can use code like below

uint count;
objc_property_t* properties = class_copyPropertyList(self.class, &count);
    NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0; i < count ; i++)
    {
        const char* propertyName = property_getName(properties[i]);
        [propertyArray addObject:[NSString  stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
    }
    free(properties);
Yas Tabasam
  • 10,517
  • 9
  • 48
  • 53
msk
  • 8,885
  • 6
  • 41
  • 72
6

You must include the runtime headers

 #import<objc/runtime.h>
uint propertiesCount;
objc_property_t *classPropertiesArray = class_copyPropertyList([self class], &propertiesCount);
free(classPropertiesArray);
Yas Tabasam
  • 10,517
  • 9
  • 48
  • 53
Vlad
  • 3,346
  • 2
  • 27
  • 39
1

The answer by @user529758 won't work with ARC and it won't list the properties of any ancestor classes.

To fix this, you need to traverse up the class hierarchy, and use the ARC-compatible [NSObject valueForKey:] to get the property values.

Person.h:

#import <Foundation/Foundation.h>

extern NSMutableArray *propertyNamesOfClass(Class klass);

@interface Person : NSObject

@property (nonatomic) NSString *name;

@end

Person.m:

#import "Person.h"
#import <objc/runtime.h>

NSMutableArray *propertyNamesOfClass(Class klass) {
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList(klass, &count);

    NSMutableArray *rv = [NSMutableArray array];

    for (unsigned int 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;
}

@implementation Person

- (NSMutableArray *)allPropertyNames {
    NSMutableArray *classes = [NSMutableArray array];
    Class currentClass = [self class];
    while (currentClass != nil && currentClass != [NSObject class]) {
        [classes addObject:currentClass];
        currentClass = class_getSuperclass(currentClass);
    }

    NSMutableArray *names = [NSMutableArray array];
    [classes enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Class currentClass, NSUInteger idx, BOOL *stop) {
        [names addObjectsFromArray:propertyNamesOfClass(currentClass)];
    }];

    return names;
}

- (NSString*)description {
    NSMutableArray *keys = [self allPropertyNames];
    NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:keys.count];
    [keys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
        properties[key] = [self valueForKey:key];
    }];

    NSString *className = NSStringFromClass([self class]);
    return [NSString stringWithFormat:@"%@ : %@", className, properties];
}

Student.h:

#import "Person.h"

@interface Student : Person

@property (nonatomic) NSString *studentID;

@end

Student.m:

#import "Student.h"

@implementation Student

@end

main.m:

#import <Foundation/Foundation.h>
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Student *student = [[Student alloc] init];
        student.name = @"John Doe";
        student.studentID = @"123456789";
        NSLog(@"student - %@", student);
    }
    return 0;
}
jlukanta
  • 159
  • 1
  • 11
-1

The solution of serhats is great unfortunately it doesn't work for iOS (as you mentioned) (and this question is tagged for iOS). A workaround would be to get a NSDictionary representation of the object and then access it normally as key-value pairs. I would recommend a category for NSObject:

Header-File:

@interface NSObject (NSDictionaryRepresentation)

/**
 Returns an NSDictionary containing the properties of an object that are not nil.
 */
- (NSDictionary *)dictionaryRepresentation;

@end

Implementation-File:

#import "NSObject+NSDictionaryRepresentation.h"
#import <objc/runtime.h>

@implementation NSObject (NSDictionaryRepresentation)

- (NSDictionary *)dictionaryRepresentation {
    unsigned int count = 0;
    // Get a list of all properties in the class.
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:count];

    for (int i = 0; i < count; i++) {
        NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
        NSString *value = [self valueForKey:key];

        // Only add to the NSDictionary if it's not nil.
        if (value)
            [dictionary setObject:value forKey:key];
    }

    free(properties);

    return dictionary;
}

@end

Borrowed from this article: http://hesh.am/2013/01/transform-properties-of-an-nsobject-into-an-nsdictionary/

This way you could do something similar as serhats mentioned:

for (NSString *key in objectDic.allKeys) {
   if([objectDic[key] isKindOfClass:[UILabel  class]])
   {
       //put attribute to your array
   }
}
palme
  • 2,499
  • 2
  • 21
  • 38