前言:
本篇文章将介绍以下两个和 Ivar Layout
有关的函数:
const uint8_t *class_getIvarLayout(Class cls)
const uint8_t *class_getWeakIvarLayout(Class cls)
说明:
介绍Ivar Layout
之前,我们先通过这篇回顾一下实例变量ivar
,我们已经可以利用ivar_getTypeEncoding
和ivar_getName
两个函数获取到变量的类型和名称。平时我们常写的属性如下:
变量类型和变量名称可以获取到了,但是strong
或是weak
却还不知道。本篇要介绍的这两个函数正是解决这个问题的。
这两个函数的返回值都是 const uint8_t *
类型,即uint8_t
数组。uint8_t
定义如下:
它本质上是unsigned char
,一般是指无符号8位整型数。一个无符号8位整型数在16进制中是两位,恰好地,这两位中的后一位表示了连续的strong
(或weak
)类型的实例变量的数量,前一位表示连续的非strong
(或weak
)类型的实例变量的数量。这一句听着有些抽象,用代码示例来看看:
初窥:
我们先定义一个类并向类中添加少量的属性:
// Dog.h 文件
@interface Dog : NSObject
@property (nonatomic, strong) id property_1_s;
@property (nonatomic, weak) id property_2_w;
@property (nonatomic, unsafe_unretained) id property_3_un;
@property (nonatomic, weak) id property_4_w;
@property (nonatomic, strong) id property_5_s;
@property (nonatomic, strong) id property_6_s;
@property (nonatomic, unsafe_unretained) id property_7_un;
@property (nonatomic, strong) id property_8_s;
@property (nonatomic, strong) id property_9_s;
@property (nonatomic, weak) id property_10_w;
@property (nonatomic, weak) id property_11_w;
@property (nonatomic, strong) id property_12_s;
@end
// Dog.m 文件
@implementation Dog
@end
为了接下来打印时方便看是strong
、weak
或unsafe_unretained
类型,在属性的最后加上了s
、w
、un
来区分。
现在将这两个函数用起来,代码示例如下:
// ViewController.m文件
printf("strong:\n");
const uint8_t *array_s = class_getIvarLayout([Dog class]);
int i = 0;
uint8_t value_s = array_s[i];
while (value_s != 0x0) {
printf("\\x%02x\n", value_s);
value_s = array_s[++i];
}
printf("----------\n");
printf("weak:\n");
const uint8_t *array_w = class_getWeakIvarLayout([Dog class]);
int j = 0;
uint8_t value_w = array_w[j];
while (value_w != 0x0) {
printf("\\x%02x\n", value_w);
value_w = array_w[++j];
}
Ivar Layout
打印结果如下:
strong:
\x01
\x32
\x12
\x21
----------
weak:
\x11
\x11
\x52
\x10
通过打印结果解释刚才那句话:
先说class_getIvarLayout
获取的结果:
第一个\x01
表示一开始有0个非strong
类型的实例变量,这是因为第一个属性property_1_s
是strong
类型的,这也正是第二位16进制那个数字1
所表示的;
接下来有三个非strong
类型的,两个strong
类型的,即\x32
;
接下来有一个非strong
类型的,两个strong
类型的,即\x12
;
接下来有两个非strong
类型的,一个strong
类型的,即\x21
;
这些正好对应了声明的所有的属性。
同理可以理解class_getWeakIvarLayout
获取的结果。
进阶:
刚才在类中添加的属性都是8字节(64位系统下)的id
类型的,现在再向类中添加如下几个其他类型的:
// 在刚才的property_12_s属性后添加
/** ----------我是分割线---------- */
@property (nonatomic, assign) int property_13_int;
@property (nonatomic, assign) short property_14_short;
@property (nonatomic, weak) id property_15_w;
@property (nonatomic, assign) char property_16_char;
@property (nonatomic, strong) id property_17_s;
现在属性一共是17个。运行程序后Ivar Layout
打印结果如下:
strong:
\x11
\x32
\x12
\x21
\x11
----------
weak:
\x21
\x11
\x52
\x11
\x10
有没有发现,这两个函数获取到的Ivar Layout
数字总和都是15,而不是属性的个数17?而且获取到的strong
类型的第一位数字就和刚才不一样(本次\x11
,刚才\x01
)?我们现在用函数class_copyIvarList
获取类的实例变量列表,看看能不能找出原因。在ViewController.m
文件中添加如下代码:
unsigned int count;
Ivar *list = class_copyIvarList([Dog class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = list[i];
const char * ivarName = ivar_getName(ivar);
NSLog(@"%s", ivarName);
}
本次打印结果如下:
runtime[10502:809019] _property_16_char
runtime[10502:809019] _property_14_short
runtime[10502:809019] _property_13_int
runtime[10502:809019] _property_1_s
runtime[10502:809019] _property_2_w
runtime[10502:809019] _property_3_un
runtime[10502:809019] _property_4_w
runtime[10502:809019] _property_5_s
runtime[10502:809019] _property_6_s
runtime[10502:809019] _property_7_un
runtime[10502:809019] _property_8_s
runtime[10502:809019] _property_9_s
runtime[10502:809019] _property_10_w
runtime[10502:809019] _property_11_w
runtime[10502:809019] _property_12_s
runtime[10502:809019] _property_15_w
runtime[10502:809019] _property_17_s
可以看到,一开始添加的id
类型的12个属性,用函数class_copyIvarList
获取到的顺序和自己写的顺序仍然一样;而后添加的其他类型的属性,在实例变量列表中的顺序和自己写的就不一样了。
用这个实例变量列表再和刚才的Ivar Layout
对照--为了检验问题是不是由基本数据类型的属性产生的,我们可以倒序着对照(即从_property_17_s
开始):
- 先倒序对照
strong
类型的,一直到_property_1_s
都没问题,_property_1_s
对应的是第一个\x11
中的后一位1
;而_property_16_char
、_property_14_short
和_property_13_int
三个实例变量共同对应前一位1
。 - 再倒序对照
weak
类型的,一直到_property_2_w
都没问题,_property_2_w
对应的是第一个\x21
中的后一位1
;而_property_16_char
、_property_14_short
和_property_13_int
三个实例变量和_property_1_s
共同对应前一位2
。
现在已经可以推测出,问题正是由基本数据类型的属性引起的。可是为什么呢?
还记着这篇文章里说到的『字节对齐』吗?在上述实例变量列表中,char
、short
、int
类型的三个实例变量在『字节对齐』下占8位,和一个id
类型的实例变量所占字节数相同,而且它们是基本数据类型,既不是strong
也不是weak
,因此刚才的Ivar Layout
就可以理解了。
读者可以再自行添加几个基本数据类型的属性进行验证,本文不再赘述。
另外,当某个属性是copy
类型时,Ivar Layout
会把它当strong
类型进行处理。
再者,将id
类型改成NSString *
、UIView *
等类型,获取结果也相同。
总结:
1. 这两个函数获取到的Ivar Layout
是和class_copyIvarList
函数获取到的实例变量列表对应的,但要注意『字节对齐』;
2. 经过测试发现,当一个类中没有strong
类型的实例变量时,用class_getIvarLayout
函数获取到的结果为NULL
,这时如果再像代码示例中array_s[i]
获取角标为0的uint8_t
类型数据时,会直接crash。class_getWeakIvarLayout
如是。所以严谨地应该先判断获取到的是否为空。