分析Runtime的属性Property
一、介绍
在OC中我们可以给任意的一个类以@property的格式声明属性,当然对于这个属性也会采用某一些属性关键字进行修饰,那么属性的真正的面目是啥样子的呢?其实,runtime源码中可以看到,property是一个结构,如下所示,只不过苹果为这个结构体另外定义了一个结构体指针。
//属性结构体指针 typedef struct objc_property *objc_property_t;
二、函数
正如我们所知,MJExtension是一个非常流行的json解析框架,其内部对对象的每一个属性进行解析的方式其实就是通过runtime来实现的。runtime源码中提供了属性的相关API函数。通过这些函数,我们可以轻松的得到每一个属性名和类型以及特性,相关函数如下:
//获取一个属性 objc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name); //添加一个属性 class_addProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount); //替换一个属性 class_replaceProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount) //获取属性数组 objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount); //获取属性名称 const char * _Nonnull property_getName(objc_property_t _Nonnull property) ; //获取属性描述名称 const char * _Nullable property_getAttributes(objc_property_t _Nonnull property); //获取属性描述数组 objc_property_attribute_t * _Nullable property_copyAttributeList(objc_property_t _Nonnull property,unsigned int * _Nullable outCount); //通过属性描述的name获取属性描述的value property_copyAttributeValue(objc_property_t _Nonnull property,const char * _Nonnull attributeName); //属性描述结构体 typedef struct { const char * _Nonnull name; /**< The name of the attribute */ const char * _Nonnull value; /**< The value of the attribute (usually empty) */ } objc_property_attribute_t;
三、案例
了解了基本函数后,现在来上手,获取并打印看看property到底是个啥样,代码如下:
//定义一个对象,声明各种属性property @interface SubObject : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) void (^Block)(void); @property (nonatomic, weak) id<NSObject> delegate; @property (nonatomic, assign) int age; @property (nonatomic, assign) float height; @property (nonatomic, assign) double weight; @property (nonatomic, strong) NSNumber *num; @property (nonatomic, strong) NSDate *date; @property (nonatomic, strong) NSData *data; @property (nonatomic, strong) NSArray *array; @property (nonatomic, strong) NSDictionary *dic; @property (nonatomic, strong) MyObject *myObject; @end
//获取属性列表 unsigned int count; objc_property_t *properties = class_copyPropertyList([subObject class], &count); for (int i=0; i<count; i++) { //获取属性 objc_property_t property = properties[i]; //属性名称 const char *propertyName = property_getName(property); NSLog(@"------propertyName = %s-------",propertyName); //属性描述名称 const char * property_attr = property_getAttributes(property); NSLog(@"------property_attr = %s-------",property_attr); //属性类型 const char *propertyType = property_copyAttributeValue(property, "T"); NSLog(@"------propertyType = %s-------",propertyType); //属性变量名称 const char *property_Value = property_copyAttributeValue(property, "V"); NSLog(@"------property_Value = %s-------",property_Value); //属性描述列表 unsigned int outCount; objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount); for (int j=0; j<outCount; j++) { objc_property_attribute_t attribute = attributeList[j]; NSLog(@"property_attribute_t_name:%s-----property_attribute_t_value:%s",attribute.name,attribute.value); } NSLog(@""); }
2019-11-10 19:18:10.335830+0800 运行时[4530:256535] ------propertyName = name------- 2019-11-10 19:18:10.336009+0800 运行时[4530:256535] ------property_attr = T@"NSString",C,N,V_name------- 2019-11-10 19:18:10.336152+0800 运行时[4530:256535] ------propertyType = @"NSString"------- 2019-11-10 19:18:10.336261+0800 运行时[4530:256535] ------property_Value = _name------- 2019-11-10 19:18:10.336405+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"NSString" 2019-11-10 19:18:10.336532+0800 运行时[4530:256535] property_attribute_t_name:C-----property_attribute_t_value: 2019-11-10 19:18:10.336648+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.336770+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_name 2019-11-10 19:18:10.336877+0800 运行时[4530:256535] 2019-11-10 19:18:10.336974+0800 运行时[4530:256535] ------propertyName = Block------- 2019-11-10 19:18:10.337256+0800 运行时[4530:256535] ------property_attr = T@?,C,N,V_Block------- 2019-11-10 19:18:10.337568+0800 运行时[4530:256535] ------propertyType = @?------- 2019-11-10 19:18:10.337972+0800 运行时[4530:256535] ------property_Value = _Block------- 2019-11-10 19:18:10.338296+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@? 2019-11-10 19:18:10.338638+0800 运行时[4530:256535] property_attribute_t_name:C-----property_attribute_t_value: 2019-11-10 19:18:10.341894+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.342363+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_Block 2019-11-10 19:18:10.342769+0800 运行时[4530:256535] 2019-11-10 19:18:10.343143+0800 运行时[4530:256535] ------propertyName = delegate------- 2019-11-10 19:18:10.343664+0800 运行时[4530:256535] ------property_attr = T@"<NSObject>",W,N,V_delegate------- 2019-11-10 19:18:10.343779+0800 运行时[4530:256535] ------propertyType = @"<NSObject>"------- 2019-11-10 19:18:10.344314+0800 运行时[4530:256535] ------property_Value = _delegate------- 2019-11-10 19:18:10.344746+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"<NSObject>" 2019-11-10 19:18:10.345172+0800 运行时[4530:256535] property_attribute_t_name:W-----property_attribute_t_value: 2019-11-10 19:18:10.345485+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.345773+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_delegate 2019-11-10 19:18:10.346062+0800 运行时[4530:256535] 2019-11-10 19:18:10.346475+0800 运行时[4530:256535] ------propertyName = age------- 2019-11-10 19:18:10.346859+0800 运行时[4530:256535] ------property_attr = Ti,N,V_age------- 2019-11-10 19:18:10.347278+0800 运行时[4530:256535] ------propertyType = i------- 2019-11-10 19:18:10.347784+0800 运行时[4530:256535] ------property_Value = _age------- 2019-11-10 19:18:10.348131+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:i 2019-11-10 19:18:10.348551+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.348980+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_age 2019-11-10 19:18:10.349373+0800 运行时[4530:256535] 2019-11-10 19:18:10.349737+0800 运行时[4530:256535] ------propertyName = height------- 2019-11-10 19:18:10.350087+0800 运行时[4530:256535] ------property_attr = Tf,N,V_height------- 2019-11-10 19:18:10.350393+0800 运行时[4530:256535] ------propertyType = f------- 2019-11-10 19:18:10.350763+0800 运行时[4530:256535] ------property_Value = _height------- 2019-11-10 19:18:10.351191+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:f 2019-11-10 19:18:10.351596+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.351971+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_height 2019-11-10 19:18:10.352397+0800 运行时[4530:256535] 2019-11-10 19:18:10.352694+0800 运行时[4530:256535] ------propertyName = weight------- 2019-11-10 19:18:10.353102+0800 运行时[4530:256535] ------property_attr = Td,N,V_weight------- 2019-11-10 19:18:10.353529+0800 运行时[4530:256535] ------propertyType = d------- 2019-11-10 19:18:10.353791+0800 运行时[4530:256535] ------property_Value = _weight------- 2019-11-10 19:18:10.354096+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:d 2019-11-10 19:18:10.354411+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.354693+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_weight 2019-11-10 19:18:10.355023+0800 运行时[4530:256535] 2019-11-10 19:18:10.355303+0800 运行时[4530:256535] ------propertyName = num------- 2019-11-10 19:18:10.355567+0800 运行时[4530:256535] ------property_attr = T@"NSNumber",&,N,V_num------- 2019-11-10 19:18:10.355887+0800 运行时[4530:256535] ------propertyType = @"NSNumber"------- 2019-11-10 19:18:10.356349+0800 运行时[4530:256535] ------property_Value = _num------- 2019-11-10 19:18:10.357784+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"NSNumber" 2019-11-10 19:18:10.358246+0800 运行时[4530:256535] property_attribute_t_name:&-----property_attribute_t_value: 2019-11-10 19:18:10.358968+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.359325+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_num 2019-11-10 19:18:10.359998+0800 运行时[4530:256535] 2019-11-10 19:18:10.360152+0800 运行时[4530:256535] ------propertyName = date------- 2019-11-10 19:18:10.360572+0800 运行时[4530:256535] ------property_attr = T@"NSDate",&,N,V_date------- 2019-11-10 19:18:10.360834+0800 运行时[4530:256535] ------propertyType = @"NSDate"------- 2019-11-10 19:18:10.361263+0800 运行时[4530:256535] ------property_Value = _date------- 2019-11-10 19:18:10.361579+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"NSDate" 2019-11-10 19:18:10.361951+0800 运行时[4530:256535] property_attribute_t_name:&-----property_attribute_t_value: 2019-11-10 19:18:10.362362+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.362838+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_date 2019-11-10 19:18:10.363256+0800 运行时[4530:256535] 2019-11-10 19:18:10.363500+0800 运行时[4530:256535] ------propertyName = data------- 2019-11-10 19:18:10.363788+0800 运行时[4530:256535] ------property_attr = T@"NSData",&,N,V_data------- 2019-11-10 19:18:10.364093+0800 运行时[4530:256535] ------propertyType = @"NSData"------- 2019-11-10 19:18:10.364430+0800 运行时[4530:256535] ------property_Value = _data------- 2019-11-10 19:18:10.364925+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"NSData" 2019-11-10 19:18:10.365110+0800 运行时[4530:256535] property_attribute_t_name:&-----property_attribute_t_value: 2019-11-10 19:18:10.365525+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.365928+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_data 2019-11-10 19:18:10.366541+0800 运行时[4530:256535] 2019-11-10 19:18:10.366648+0800 运行时[4530:256535] ------propertyName = array------- 2019-11-10 19:18:10.366929+0800 运行时[4530:256535] ------property_attr = T@"NSArray",&,N,V_array------- 2019-11-10 19:18:10.367389+0800 运行时[4530:256535] ------propertyType = @"NSArray"------- 2019-11-10 19:18:10.367686+0800 运行时[4530:256535] ------property_Value = _array------- 2019-11-10 19:18:10.368112+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"NSArray" 2019-11-10 19:18:10.368357+0800 运行时[4530:256535] property_attribute_t_name:&-----property_attribute_t_value: 2019-11-10 19:18:10.368664+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.368905+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_array 2019-11-10 19:18:10.369409+0800 运行时[4530:256535] 2019-11-10 19:18:10.369565+0800 运行时[4530:256535] ------propertyName = dic------- 2019-11-10 19:18:10.369810+0800 运行时[4530:256535] ------property_attr = T@"NSDictionary",&,N,V_dic------- 2019-11-10 19:18:10.370092+0800 运行时[4530:256535] ------propertyType = @"NSDictionary"------- 2019-11-10 19:18:10.370366+0800 运行时[4530:256535] ------property_Value = _dic------- 2019-11-10 19:18:10.370706+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"NSDictionary" 2019-11-10 19:18:10.371128+0800 运行时[4530:256535] property_attribute_t_name:&-----property_attribute_t_value: 2019-11-10 19:18:10.371537+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.371911+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_dic 2019-11-10 19:18:10.372340+0800 运行时[4530:256535] 2019-11-10 19:18:10.372762+0800 运行时[4530:256535] ------propertyName = myObject------- 2019-11-10 19:18:10.373469+0800 运行时[4530:256535] ------property_attr = T@"MyObject",&,N,V_myObject------- 2019-11-10 19:18:10.373751+0800 运行时[4530:256535] ------propertyType = @"MyObject"------- 2019-11-10 19:18:10.374167+0800 运行时[4530:256535] ------property_Value = _myObject------- 2019-11-10 19:18:10.374515+0800 运行时[4530:256535] property_attribute_t_name:T-----property_attribute_t_value:@"MyObject" 2019-11-10 19:18:10.374742+0800 运行时[4530:256535] property_attribute_t_name:&-----property_attribute_t_value: 2019-11-10 19:18:10.375014+0800 运行时[4530:256535] property_attribute_t_name:N-----property_attribute_t_value: 2019-11-10 19:18:10.375595+0800 运行时[4530:256535] property_attribute_t_name:V-----property_attribute_t_value:_myObject 2019-11-10 19:18:10.375718+0800 运行时[4530:256535]
四、分析
看到上述打印,发现一些特性,可以获取到属性名称、属性类型,属性原子性、属性引用情况等,这些基本都在属性描述property_attr中,并使用一些特别的字符表示,例如T、@,&,N,V等,每一个属性描述都是以字符“T”开头,以字符“V下划线变量名 V_xxx”结尾 (结尾不固定,对于其他属性)。
例如属性name的属性描述打印:
//T: Type(@encode) 属性类型 //@: 对象类型, NSString类型 //C: Copy 拷贝特性 //N: nonatomic 非原子性,默认为atomic,atomic为空 //V:variable 变量 _name , 使用@property修饰的属性系统默认@synthesize定义下划线变量,_name property_attr = T@"NSString",C,N,V_name
例如属性age的属性描述打印:
//T: Type(@encode) 属性类型 //i: 基本数据类型, int类型//N: nonatomic 非原子性,默认为atomic,atomic为空 //V:variable 变量 _age , 使用@property修饰的属性系统默认@synthesize定义下划线变量,_age property_attr = Ti,N,V_age-------
例如属性myObject的属性描述打印:
//T: Type(@encode) 属性类型 //@: 对象类型, 自定义类MyObject类型 //&: 引用特性, 强引用 //N: nonatomic 非原子性,默认为atomic,atomic为空 //V:variable 变量 _myObject-- , 使用@property修饰的属性系统默认@synthesize定义下划线变量,_myObject-- property_attr = T@"MyObject",&,N,V_myObject-------
就不一一列举了,基本属性特性大概如下,可能不完整:
//基本字符表示 T 是固定开头,表示类型,后跟类型 @、i、f、d、#等 & 代表强引用 C 代表copy R 代表readOnly属性,readwrite时为空 W 代表weak,assign为空,默认为assign。 N 区分的nonatomic和atomic,默认为atomic,atomic为空,’N’代表是nonatomic D @dynamic 动态特性,动态绑定,需要手动生成setter和getter方法 V_exprice 固定结尾,V代表变量,后面紧跟着的是成员变量名,代表这个property的成员变量名为_exprice。
属性类型的表示字符特性编码苹果官方给了一套参考:特性编码解析大全
//编码值 含意 //c 代表char类型 //i 代表int类型 //s 代表short类型 //l 代表long类型,在64位处理器上也是按照32位处理 //q 代表long long类型 //C 代表unsigned char类型 //I 代表unsigned int类型 //S 代表unsigned short类型 //L 代表unsigned long类型 //Q 代表unsigned long long类型 //f 代表float类型 //d 代表double类型 //B 代表C++中的bool或者C99中的_Bool //v 代表void类型 //* 代表char *类型 //@ 代表对象类型 //# 代表类对象 (Class) //: 代表方法selector (SEL) //[array type] 代表array //{name=type…} 代表struct结构体 //(name=type…) 代表union共同体 //bnum A bit field of num bits,代表位字段 //^type A pointer to type,代表指针类型 //? An unknown type (among other things, this code is used for function pointers),代表未知类型,例如block
四、扩展
类的属性声明
//类拥有一个属性 NS_ASSUME_NONNULL_BEGIN @interface MyObject : NSObject @property (nonatomic, copy) NSString *name; @end
1、获取
-(void)printPropertyWithObj:(MyObject *)myObject{ unsigned int count; objc_property_t *properties = class_copyPropertyList([myObject class], &count); for (int i=0; i<count; i++) { //获取属性 objc_property_t property = properties[i]; //属性名称 const char *propertyName = property_getName(property); NSLog(@"------propertyName = %s-------",propertyName); //属性变量 char *property_Value = property_copyAttributeValue(property, "V"); NSLog(@"------property_Value = %s-------",property_Value); //属性类型 char *property_Type = property_copyAttributeValue(property, "T"); NSLog(@"------property_Type = %s-------",property_Type); } }
2019-11-10 21:57:24.258232+0800 运行时[6007:365556] ------propertyName = name------- 2019-11-10 21:57:24.258412+0800 运行时[6007:365556] ------property_Value = _name------- 2019-11-10 21:57:24.258541+0800 运行时[6007:365556] ------property_Type = @"NSString"-------
2、添加
//添加属性 //* @param cls The class to modify. //* @param name The name of the property. //* @param attributes An array of property attributes. //* @param attributeCount The number of attributes in \e attributes. //class_addProperty(Classcls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount) const char * propertyName = "age"; objc_property_attribute_t type = { "T", "i" }; //int objc_property_attribute_t ownership = { "N" }; //nonatomic objc_property_attribute_t variable = { "V", "_age" }; //_age objc_property_attribute_t attrs[] = { type, ownership, variable }; BOOL addSuccess = class_addProperty([myObject class], propertyName, attrs, 3); if (addSuccess) { [self printPropertyWithObj:myObject]; }
2019-11-10 22:00:12.120742+0800 运行时[6064:369407] 默认属性如下: 2019-11-10 22:00:12.120941+0800 运行时[6064:369407] ------propertyName = name------- 2019-11-10 22:00:12.121073+0800 运行时[6064:369407] ------property_Value = _name------- 2019-11-10 22:00:12.121199+0800 运行时[6064:369407] ------property_Type = @"NSString"------- 2019-11-10 22:00:12.121295+0800 运行时[6064:369407] 2019-11-10 22:00:12.121405+0800 运行时[6064:369407] 添加属性如下: 2019-11-10 22:00:12.121617+0800 运行时[6064:369407] ------propertyName = age------- 2019-11-10 22:00:12.121731+0800 运行时[6064:369407] ------property_Value = _age------- 2019-11-10 22:00:12.121886+0800 运行时[6064:369407] ------property_Type = i------- 2019-11-10 22:00:12.122279+0800 运行时[6064:369407] ------propertyName = name------- 2019-11-10 22:00:12.122780+0800 运行时[6064:369407] ------property_Value = _name------- 2019-11-10 22:00:12.123222+0800 运行时[6064:369407] ------property_Type = @"NSString"-------
3、替换
//替换属性【注意:无法改变属性名称,只是修改生成的下划线变量,_variable】 //* @param cls The class to modify. //* @param name The name of the property. //* @param attributes An array of property attributes. //* @param attributeCount The number of attributes in \e attributes. //class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount) const char * propertyName = "name"; objc_property_attribute_t type = { "T", "@\"NSString\"" };//NSString objc_property_attribute_t ownership = { "C", "N" }; // copy、nonatomic objc_property_attribute_t variable = { "V", "_newName" }; //_newName objc_property_attribute_t attrs[] = { type, ownership, variable }; class_replaceProperty([myObject class], propertyName, attrs, 3); [self printPropertyWithObj:myObject];
2019-11-10 22:03:52.448875+0800 运行时[6111:373162] 默认属性如下: 2019-11-10 22:03:52.450451+0800 运行时[6111:373162] ------propertyName = name------- 2019-11-10 22:03:52.452714+0800 运行时[6111:373162] ------property_Value = _name------- 2019-11-10 22:03:52.453001+0800 运行时[6111:373162] ------property_Type = @"NSString"------- 2019-11-10 22:03:52.453125+0800 运行时[6111:373162] 2019-11-10 22:03:52.458433+0800 运行时[6111:373162] 替换属性如下: 2019-11-10 22:03:52.458658+0800 运行时[6111:373162] ------propertyName = name------- 2019-11-10 22:03:52.463156+0800 运行时[6111:373162] ------property_Value = _newName------- 2019-11-10 22:03:52.463973+0800 运行时[6111:373162] ------property_Type = @"NSString"-------
程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!