runtime使用篇: class_getIvarLayout 和 class_getWeakIvarLayout

前言:

本篇文章将介绍以下两个和 Ivar Layout 有关的函数:
const uint8_t *class_getIvarLayout(Class cls)
const uint8_t *class_getWeakIvarLayout(Class cls)

说明:

介绍Ivar Layout之前,我们先通过这篇回顾一下实例变量ivar,我们已经可以利用ivar_getTypeEncodingivar_getName两个函数获取到变量的类型和名称。平时我们常写的属性如下:

属性

变量类型和变量名称可以获取到了,但是strong或是weak却还不知道。本篇要介绍的这两个函数正是解决这个问题的。
这两个函数的返回值都是 const uint8_t * 类型,即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

为了接下来打印时方便看是strongweakunsafe_unretained类型,在属性的最后加上了swun来区分。
现在将这两个函数用起来,代码示例如下:

// 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_sstrong类型的,这也正是第二位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

现在已经可以推测出,问题正是由基本数据类型的属性引起的。可是为什么呢?
还记着这篇文章里说到的『字节对齐』吗?在上述实例变量列表中,charshortint类型的三个实例变量在『字节对齐』下占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如是。所以严谨地应该先判断获取到的是否为空。

推荐阅读更多精彩内容