我们知道,Class的本质是一个结构体objc_class,而这个结构体又继承自objc_object,所以可以说,类本身也是一个对象,它也有isa指针,那么类的isa指向什么地方呢?我们来探索一下。
isa
创建一个XXPerson对象:
看打印:
通过上面操作,我们看到,类的isa指向了一个同名的类,这两个类的地址不同,所指向的类,是系统自动创建的,叫做元类。
所以,对象isa->类,类isa->元类,类本身也是一个对象。
那么,元类的isa又指向什么地方呢?继续探索。
我们发现元类的isa指向的是一个NSObject的类,但是它和我们熟悉的NSObject不是同一个地址,我们称其为根元类。又发现,NSObject的isa指向的也是这个根元类。
那么,根元类的isa又指向何处呢?
可见,根元类的isa指向的是它自己。
superclass
了解了元类的isa之后,我们来看下元类的父类。
这里有两个类,XXTeacher和XXPerson,XXTeacher是XXPerson的子类,XXPerson是NSObject的子类。
运行结果如下:
对比打印的地址,我们可以得出这样一个结论:
子类的元类的父类就是其父类的元类。
结合上面讲到的isa,我们有下图这样的结论:
结构体objc_class
我们先看下bits里面有啥,其中isa指针占8个字节,superclass占8个字节,cache占16个字节,所以要想获取bits的信息,要将类的地址平移32个字节即可。
通过操作,我们获得了一个class_data_bits_t的信息,但是bits = 4308618804是啥意思呢?看不太懂,那就看看class_data_bits_t里面有啥。
class_data_bits_t里面有个data()方法,返回一个class_rw_t结构体,这个class_rw_t里包含类对象的实例方法、协议等信息。
又看不懂了,咋办?我们看到class_rw_t结构体里有methods()、properties()、protocols()这些方法,这应该就是实例对象的方法、属性和协议吧!
我们试一下methods()!
走到这里,我们看到一个count=9,这个count表示的就是这个类的实例方法的数量。
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short gender;
XXPerson类有4个属性,对应的getter和setter方法,共计8个,未添加其它实例方法,那还有一个方法是什么呢?我们尝试把这9个方法都打印出来!
methods()返回的是一个method_array_t类的对象,method_array_t继承自list_array_tt。
看上图,我们得到的$8是一个method_list_t结构体,其继承自entsize_list_tt。entsize_list_tt可以理解成是一个模板,它可以实例化mothed_list_t、property_list_t等。其下有个get(uint32_t i)方法,试一下!
发现并未获取到什么,但是返回的是一个method_t类型的。method_t里面有个big结构体,里面包含SEL和IMP,这不正是我们想要的吗!!!
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
试一下:
成功!
注意:这里说明一下,因为我的mac的处理器是Intel的,所以这里可以用big方法,如果电脑是m1的,就不能使用big了。关于这点后续我再补充!
通过修改get方法的参数打印出了所有的9个方法,从而确定之前说的多出来的那个方法是".cxx_destruct",这个方法是在ARC下用来释放成员变量的。
我们可以用同样的方式来获取属性,按照获取实例方法的步骤,将methods()改成调properties()方法,最后get方法可以直接获取到属性。
总结,本文主要介绍了类和元类的isa指向,以及元类之间的父子关系等,那张isa和superclass的综合关系图可以牢记于心。此外,还介绍了类中的bits信息,其中包含如何获取类的实例方法和属性。关于类的其它信息,后续再慢慢补充!