runtime:class_copyPropertyList 不能获取父类属性问题

1,631 阅读2分钟

class_copyPropertyList

  • class_copyPropertyList是来获取属性的
  • 只能获取到 @property 声明的属性

使用runtime获取类私有属性,首先需要导入 #import <objc/runtime.h>

获取属性列表的方法是

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

举个例子

Person 声明两个属性name和age

#import "Person.h"

@interface Person ()

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *age;

@end

@implementation Person

@end

使用 class_copyPropertyList 方法获取Person的私有属性

Person *person = [[Person alloc] init];
    id personClass = objc_getClass("Person");
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(personClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
        id value = [person valueForKey:propName];
        fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
    }

打印结果如下

name T@"NSString",C,N,V_nameage T@"NSString",C,N,V_age

但是有一个问题,如果类从其它的类继承过来的,父类的属性将不会被copy出来

举个例子

@interface Son : Person

@end

@interface Son ()

@property (nonatomic, copy) NSString *birthday;
@property (nonatomic, copy) NSString *sex;

@end

@implementation Son

@end

Son类继承Person类,我们同样的方法打印一下Son类的属性列表,结果如下

birthday T@"NSString",C,N,V_birthday
sex T@"NSString",C,N,V_sex

可以看到只能获取Son类自己的属性,不能获取父类的name和age属性,但项目中使用继承的地方非常多,比如使用AOP实现无埋点时就需要获取父类的属性,那我们怎么获取父类的属性列表呢?

1. 最先想到的方案就是用上述方法 获取的 [son superclass] 的属性列表。

  objc_property_t *properties = class_copyPropertyList([son superclass], &outCount);

打印结果:

name T@"NSString",C,N,V_name
age T@"NSString",C,N,V_age</span> 

2. 还有一种方法是声明一个Protocol, Protocol中有属性,然后获取Protocol中属性列表,方法如下:

objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)  
Protocol *objc_getProtocol(const char *name) 

很多场景下我们需要同时获取子类和父类的私有属性,封装了两个方法方便调用

获取指定类的属性以及父类的所有属性

/**
 获取指定类的属性

 @param cls 被获取属性的类
 @return 属性名称 [NSString *]
 */
NSArray * getClassProperty(Class cls) {

    if (!cls) return @[];

    NSMutableArray * all_p = [NSMutableArray array];

    unsigned int a;

    objc_property_t * result = class_copyPropertyList(cls, &a);

    for (unsigned int i = 0; i < a; i++) {
        objc_property_t o_t =  result[i];
        [all_p addObject:[NSString stringWithFormat:@"%s", property_getName(o_t)]];
    }

    free(result);

    return [all_p copy];
}

/**
 获取指定类(以及其父类)的所有属性

 @param cls 被获取属性的类
 @param until_class 当查找到此类时会停止查找,当设置为 nil 时,默认采用 [NSObject class]
 @return 属性名称 [NSString *]
 */
NSArray * getAllProperty(Class cls, Class until_class) {

    Class stop_class = until_class ?: [NSObject class];

    if (class_getSuperclass(cls) == stop_class) return @[];

    NSMutableArray * all_p = [NSMutableArray array];

    [all_p addObjectsFromArray:getClassProperty(cls)];

    if (class_getSuperclass(cls) == stop_class) {
        return [all_p copy];
    } else {
        [all_p addObjectsFromArray:getAllProperty([cls superclass], stop_class)];
    }

    return [all_p copy];
}

还有一种场景,我们只知道对象名,不仅要获取对象的私有属性,还要获取属性的内容,可以调用下面的方法

/**
 获取对象的所有属性和属性内容

 @param obj 对象 @return 所有属性及属性内容 [NSDictionary *] */
+ (NSDictionary *)getAllPropertiesAndVaules:(NSObject *)obj
{
    NSMutableDictionary *propsDict = [NSMutableDictionary dictionary];
    unsigned int outCount;
    objc_property_t *properties =class_copyPropertyList([obj class], &outCount);
    for ( int i = 0; i<outCount; i++)
    {
        objc_property_t property = properties[i];
        const char* char_f =property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        id propertyValue = [obj valueForKey:(NSString *)propertyName];
        if (propertyValue) {
            [propsDict setObject:propertyValue forKey:propertyName];
        }
    }
    free(properties);
    return propsDict;
} 

这里延伸一下另外一个方法,获取私有变量的方法 class_copyIvarList

class_copyIvarList

  • class_copyIvarList 用来获取所有的变量的
  • 获取所有的变量,包括 @property 声明的属性和声明的全局变量 如: _name

获取指定类以及其父类所有的变量

/**
 获取指定类的变量

 @param cls 被获取变量的类
 @return 变量名称集合 [NSString *]
 */
NSArray * getClassIvar(Class cls) {

    if (!cls) return @[];

    NSMutableArray * all_p = [NSMutableArray array];

    unsigned int a;

    Ivar * iv = class_copyIvarList(cls, &a);

    for (unsigned int i = 0; i < a; i++) {
        Ivar i_v = iv[i];
        [all_p addObject:[NSString stringWithFormat:@"%s", ivar_getName(i_v)]];
    }

    free(iv);

    return [all_p copy];
}

/**
 获取指定类(以及其父类)的所有变量

 @param cls 被获取变量的类
 @param until_class 当查找到此类时会停止查找,当设置为 nil 时,默认采用 [NSObject class]
 @return 变量名称集合 [NSString *]
 */
NSArray * getAllIvar(Class cls, Class until_class) {

    Class stop_class = until_class ?: [NSObject class];

    if (class_getSuperclass(cls) == stop_class) return @[];

    NSMutableArray * all_p = [NSMutableArray array];

    [all_p addObjectsFromArray:getClassIvar(cls)];

    if (class_getSuperclass(cls) == stop_class) {
        return [all_p copy];
    } else {
        [all_p addObjectsFromArray:getAllIvar([cls superclass], stop_class)];
    }

    return [all_p copy];
}

总结

  • class_copyPropertyList 只能获取到 @property 声明的属性

  • class_copyIvarList 用来获取所有的变量的

  • 以上两个方法都只能获取到当前类的属性和变量,获取不到父类的属性和变量

  • class_copyMethodList 是获取类的所有方法的