【问题标题】:List of class properties in Objective-CObjective-C 中的类属性列表
【发布时间】:2012-08-02 08:59:03
【问题描述】:

有没有办法获取某种类属性的数组?例如,如果我有这样的界面

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

我可以在不知道它们的名字的情况下获得对这些标签的引用吗?

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

我知道我可以使用 [NSArray arrayWithObjects:firstLabel,secondLabel,nil] 轻松做到这一点,但我想使用 for (UILabel* oneLabel in ???[self objects]???) 之类的某种类枚举来做到这一点

【问题讨论】:

标签: objective-c ios class object


【解决方案1】:

所以更准确地说,如果我理解正确的话,您想要 动态的、运行时观察 属性。做这样的事情(在 self 上实现这个方法,你想自省的类):

#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);
}

像这样使用它:

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];

希望这会有所帮助。

【讨论】:

  • 这真的很好用!只是,在此之后我如何访问这些属性?这给了我一串名字,但不是指向对象的实际指针……我猜它一定很容易:)我只是不熟悉 objc/runtime 类:) 谢谢!
  • 一旦你有了字符串,使用KVC和[self valueForKey:propertyName];可能更容易。通常使用访问器比直接使用支持 ivar 更好 - 因此您可以利用统一访问原则并给自己一些挂钩以便稍后挂上您的帽子。
  • 不要用get前缀命名方法;这仅限于一个非常具体的用例,不是这样。
  • @H2CO3 - (嘿伙计)我相信 class_copyPropertyList 只列出了当前 Class 对象的属性,如果你想走上继承链,就需要一些递归。 .这是根据文档,但是我没有测试,因为我最终在我正在做的事情中使用了另一种方法。
  • allPropertyNames 和 [self valueForKey:propertyName], [self setValue:@"" forKey:propertyName] 的组合对我有用。在 ARC 下使用 pointerOfIvarForPropertyNamed 时出错。
【解决方案2】:

使用 NSObject 的attributeKeys 方法。

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

        id attribute = [self valueForKey:key];

        if([attribute isKindOfClass:[UILabel  class]])
        {
         //put attribute to your array
        }
    }

【讨论】:

  • 这看起来是最优雅的解决方案,但遗憾的是方法 attributeKeys 仅适用于 Mac OS X,不适用于 iOS...
【解决方案3】:

看看这个link。它是目标 C 运行时的目标 c 包装器。

您可以使用如下代码

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);

【讨论】:

  • 别忘了#import
【解决方案4】:

您必须包含运行时标头

 #import<objc/runtime.h>
uint propertiesCount;
objc_property_t *classPropertiesArray = class_copyPropertyList([self class], &propertiesCount);
free(classPropertiesArray);

【讨论】:

    【解决方案5】:

    @user529758 的回答不适用于 ARC,也不会列出任何祖先类的属性。

    要解决此问题,您需要向上遍历类层次结构,并使用与 ARC 兼容的[NSObject valueForKey:] 来获取属性值。

    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];
    }
    

    学生.h:

    #import "Person.h"
    
    @interface Student : Person
    
    @property (nonatomic) NSString *studentID;
    
    @end
    

    学生.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;
    }
    

    【讨论】:

      【解决方案6】:

      serhats 的解决方案很棒,不幸的是它不适用于 iOS(正如你提到的)(并且这个问题被标记为 iOS)。一种解决方法是获取对象的 NSDictionary 表示,然后将其作为键值对正常访问。我会为 NSObject 推荐一个类别:

      头文件:

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

      实施文件:

      #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
      

      借自这篇文章:http://hesh.am/2013/01/transform-properties-of-an-nsobject-into-an-nsdictionary/

      这样你就可以做类似 serhats 提到的事情:

      for (NSString *key in objectDic.allKeys) {
         if([objectDic[key] isKindOfClass:[UILabel  class]])
         {
             //put attribute to your array
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-05
        • 2011-12-24
        • 2011-07-31
        • 1970-01-01
        • 2010-10-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多