本章的主要内容将聚集在Runtime对成员变量与属性的处理。在讨论之前,我们先介绍一个重要的概念:类型编码
类型编码(Type Encoding)
作为对Runtime的补充,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。这种编码方案在其它情况下也是非常有用的,因此我们可以使用@encode编译器指令来获取它。当给定一个类型时,@encode返回这个类型的字符串编码。这些类型可以是诸如int、指针这样的基本类型,也可以是结构体、类等类型。事实上,任何可以作为sizeof()操作参数的类型都可以用于@encode()。
在Objective-C Runtime Programming Guide中的Type Encoding一节中,列出了Objective-C中所有的类型编码。需要注意的是这些类型很多是与我们用于存档和分发的编码类型是相同的。
举例
float arr[] = {1.0, 2.0, 3.0, 4.0};
NSLog(@"array encoding type:%s", @encode(typeof(arr)));
NSLog(@"NSInteger encoding type:%s", @encode(NSInteger));
NSLog(@"NSString encoding type:%s", @encode(NSString));
打印结果如下
2015-09-01 22:48:56.044 runtime[4234:193020] array encoding type:[4f]
2015-09-01 22:48:56.045 runtime[4234:193020] NSInteger encoding type:q
2015-09-01 22:48:56.045 runtime[4234:193020] NSString encoding type:{NSString=#}
对于属性而言,还会有一些特殊的类型编码,以表明属性是只读、拷贝、retain等等,详情可以参考Property Type String。
先定义一个类
@interface Person : NSObject
{
NSInteger _age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double weight;
@property (nonatomic, assign) double height;
@end
成员变量
基础数据类型
Ivar是表示实例变量的类型,其实际是一个指向objc_ivar结构体的指针,其定义如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 变量名
char *ivar_type OBJC2_UNAVAILABLE; // 变量类型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字节
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
操作函数
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
<pre><code>// 获取成员变量名
const char * ivar_getName ( Ivar v );
// 获取成员变量类型编码
const char * ivar_getTypeEncoding ( Ivar v );
// 获取成员变量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
/**
* 设置一个实例对象某个成员属性的值
*
* @param obj 对象
* @param ivar 成员属性
* @param value 成员属性的值
*/
void object_setIvar(id obj, Ivar ivar, id value);
// 获取实例对象某个成员属性的值
id object_getIvar(id obj, Ivar ivar)
** class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
** class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。
** class_addIvar函数,Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,这时我们就可以使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。
** class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
示例代码:
Class cls = [Person class];
size_t size = class_getInstanceSize(cls);
NSLog(@"size = %zu", size);
unsigned int ivarCount = 0;
Ivar *ivarList = class_copyIvarList(cls, &ivarCount);
NSLog(@"*************** IvarList ********************");
NSLog(@"ivarCount = %d", ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivarList[i];
const char *ivar_name = ivar_getName(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
ptrdiff_t offset = ivar_getOffset(ivar);
NSLog(@"ivar_name = %s, typeEncoding = %s, offset = %td", ivar_name, typeEncoding, offset);
}
// 一定要对ivarList做free操作
free(ivarList);
NSLog(@"****************** 设置、获取成员变量的值 ***********************");
Person *p = [[Person alloc] init];
Ivar ivar_name = class_getInstanceVariable(cls, "_name");
object_setIvar(p, ivar_name, @"傻傻木头人");
NSLog(@"-----object_getIvar------name = %@", object_getIvar(p, ivar_name));
打印结果如下:
2015-09-05 23:37:32.526 runtime[10233:304437] size = 40
2015-09-05 23:37:32.527 runtime[10233:304437] *************** IvarList ********************
2015-09-05 23:37:32.528 runtime[10233:304437] ivarCount = 4
2015-09-05 23:37:32.528 runtime[10233:304437] ivar_name = _age, typeEncoding = q, offset = 8
2015-09-05 23:37:32.528 runtime[10233:304437] ivar_name = _name, typeEncoding = @"NSString", offset = 16
2015-09-05 23:37:32.528 runtime[10233:304437] ivar_name = _weight, typeEncoding = d, offset = 24
2015-09-05 23:37:32.528 runtime[10233:304437] ivar_name = _height, typeEncoding = d, offset = 32
2015-09-05 23:37:32.528 runtime[10233:304437] ****************** 设置、获取成员变量的值 ***********************
2015-09-05 23:37:32.529 runtime[10233:304437] -----object_getIvar------name = 傻傻木头人
属性
基本数据类型
objc_property_t
objc_property_t是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针,其定义如下:
typedef struct objc_property *objc_property_t;
objc_property_attribute_t
objc_property_attribute_t定义了属性的特性(attribute),它是一个结构体,定义如下
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
操作函数
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );
// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 获取属性名
const char * property_getName ( objc_property_t property );
// 获取属性特性描述字符串
const char * property_getAttributes ( objc_property_t property );
// 获取属性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );
// 获取属性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
** class_copyPropertyList、property_copyAttributeList 函数,返回的数组在使用完后一定要调用free()释放,防止内存泄露。
实例代码
Class cls = [Person class];
unsigned int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList(cls, &propertyCount);
NSLog(@"********************PropertyList******************");
NSLog(@"propertyCount = %d", propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = propertyList[i];
const char *property_name = property_getName(property);
const char *property_attributes = property_getAttributes(property);
NSLog(@"******* property_name = %s, property_attributes = %s", property_name, property_attributes);
unsigned int attributeCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &attributeCount);
NSLog(@"** attributeList ---- attributeCount = %u", attributeCount);
for (int i = 0; i < attributeCount; i++) {
objc_property_attribute_t attribute = attributeList[i];
NSLog(@"name = %s, value = %s" ,attribute.name, attribute.value);
}
free(attributeList); // 一定要对attributeList做free操作
}
free(propertyList);// 一定要对ivarList做free操作
打印结果
2015-09-07 22:43:43.019 runtime[3250:176687] ********************PropertyList******************
2015-09-07 22:43:43.020 runtime[3250:176687] propertyCount = 3
2015-09-07 22:43:43.020 runtime[3250:176687] ******* property_name = name, property_attributes = T@"NSString",C,N,V_name
2015-09-07 22:43:43.021 runtime[3250:176687] ** attributeList ---- attributeCount = 4
2015-09-07 22:43:43.021 runtime[3250:176687] name = T, value = @"NSString"
2015-09-07 22:43:43.021 runtime[3250:176687] name = C, value =
2015-09-07 22:43:43.021 runtime[3250:176687] name = N, value =
2015-09-07 22:43:43.021 runtime[3250:176687] name = V, value = _name
2015-09-07 22:43:43.021 runtime[3250:176687] ******* property_name = weight, property_attributes = Td,N,V_weight
2015-09-07 22:43:43.022 runtime[3250:176687] ** attributeList ---- attributeCount = 3
2015-09-07 22:43:43.022 runtime[3250:176687] name = T, value = d
2015-09-07 22:43:43.022 runtime[3250:176687] name = N, value =
2015-09-07 22:43:43.022 runtime[3250:176687] name = V, value = _weight
2015-09-07 22:43:43.022 runtime[3250:176687] ******* property_name = height, property_attributes = Td,N,V_height
2015-09-07 22:43:43.022 runtime[3250:176687] ** attributeList ---- attributeCount = 3
2015-09-07 22:43:43.022 runtime[3250:176687] name = T, value = d
2015-09-07 22:43:43.023 runtime[3250:176687] name = N, value =
2015-09-07 22:43:43.034 runtime[3250:176687] name = V, value = _height
小结
本节讲述了类的成员变量和属性相关内容,成员变量和属性是类数据结构的基础,这些有助于对类的本质更深的认识。