在上篇文章中我们了解了alloc创建对象的过程,我们知道了对象里面含有一个叫做isa的指针,并且通过isa指针,从对象找到了他所属的类,今天我们就来探索一下关于类的秘密。
首先和NSObject一样,我们先看一下Class到底是什么,在源码的objc.h中我们找到了typedef struct objc_class *Class;,这说明Class实际上是objc_class,在objc-rumtime-new.h中我们找到了他的定义struct objc_class : objc_object,他同样也继承了objc_object,所有我们说类也是一个对象,就是我们常说的类对象。
我们可以通过不同的方式,多创建几个类,并且打印出他的地址,其结果如下
Class class1 = [WTPerson class];
Class class2 = [WTPerson alloc].class;
Class class3 = object_getClass([WTPerson alloc]);
NSLog(@"\n%p -- %p -- %p", class1, class2, class3);
0x100008960 -- 0x100008960 -- 0x100008960
我们发现所有的类打印输出的结果都是0x100008960这个地址,这就说明同一个类,在内存中只有一个地址。
那么0x100008960这个地址就是类对象的isa指针,这个isa指针又是指向哪里的呢?我们使用x/4gx来打印一下class1这个类内存地址看一下:
我们发现类对象的isa指向的内存地址使用
ISA_MASK获取后同样获取到的是WTPerson,我们知道class1也是WTPerson,可是两个的地址空间是不一样的,class1的是0x00008960,isa指针指向的是0x00008938,这我们猜测类对象的isa指针指向的是类对象的元类。那元类的isa指针指向哪里呢,我们继续x/4gx:
我们发现元类的isa指针指向的是
NSObject,我们打印NSObject.class,发现两个地址也是不一样的,所以我们猜测元类的isa指针指向的是NSObject的元类,同时我们打印NSObject的元类,发现他的isa指针地址和本身是一样的,即NSObject的元类的isa指针指向自己了,所以NSObject的元类也就是常说的根元类。NSObject也是类对象,他的isa指针指向的就是类对象的元类,也就是指向根元类。
在之前的探索中,我们知道,类在底层中实际上是objc_class这个结构,那么我们就来看看这个结构的组成
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass; //指向父类的指针
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 类存储信息的地方
……其他的方法……
}
复制代码
isa指针在上面我们已经进行过探索了,我们接下来探索一下superclass指针,我们添加一个子类来,来看一下指向父类的指针,同时也探索一下元类使用是否也是相同的继承关系。
在上图中,我们可以了解到student的superclass关系是:
student superclass -> person superclass -> NSObject superclass -> nil
元类的指针是:student元类 isa -> NSObject元类,也就是说所有的元类都是指向的根元类,元类的isa指针没有继承关系。
那元类的父类是否有继承关系呢?我们重新梳理一下代码:
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject 类对象
Class obj_class = object_getClass(object1);
// NSObject 元类对象
Class meta_class = object_getClass(obj_class);
NSLog(@"\nNSObject实例对象 - %p \n",object1);
NSLog(@"NSObject类对象 - %p \n",obj_class);
NSLog(@"NSObject元类对象 - %p \n",meta_class);
// WTPerson
Class p_meta_class = objc_getMetaClass("WTPerson");
Class p_super_class = class_getSuperclass(p_meta_class);
NSLog(@"\nWTPerson元类:%@ - %p\n",p_meta_class, p_meta_class);
NSLog(@"WTPerson元类的父类:%@ - %p\n",p_super_class, p_super_class);
// WTStudent
Class st_meta_class = objc_getMetaClass("WTStudent");
Class st_super_class = class_getSuperclass(st_meta_class);
NSLog(@"\nWTStudent元类:%@ - %p\n",st_meta_class, st_meta_class);
NSLog(@"WTStudent元类的父类:%@ - %p\n",st_super_class, st_super_class);
// NSObject 的父类
Class objc_super_class = class_getSuperclass(NSObject.class);
NSLog(@"NSObject的父类:%@ - %p\n",objc_super_class, objc_super_class);
// 根元类 的父类
Class root_super_class = class_getSuperclass(meta_class);
NSLog(@"根元类的父类:%@ - %p\n",root_super_class, root_super_class);
打印结果如下:
NSObject实例对象 - 0x10070dca0
NSObject类对象 - 0x10036e140
NSObject元类对象 - 0x10036e0f0
WTPerson元类:WTPerson - 0x100008938
WTPerson元类的父类:NSObject - 0x10036e0f0
WTStudent元类:WTStudent - 0x100008848
WTStudent元类的父类:WTPerson - 0x100008938
NSObject的父类:(null) - 0x0
根元类的父类:NSObject - 0x10036e140
由此可知,元类的父类就是父类的元类,根元类的父类就是NSObject,NSObject的父类是nil。
由此superclass属性我们了解了,接下来我们来瞧一瞧
class_data_bits_t bits这个类存储信息的地方,
首先查看其内存结构:
struct class_data_bits_t {
friend objc_class;
......省略部分......
class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); }
......省略部分......
}
friend关键字是c++中的友元函数,其可以把一些函数(包括全局函数和其他类的成员函数)声明为“友元”,这样那些函数就成为该类的友元函数,在友元函数内部就可以访问该类对象的私有成员了。大概了解下面我们主要研究最主要的方法class_rw_t* data(),其返回的是我们比较熟悉的class_rw_t*,继续看其内存结构:
struct class_rw_t {
......省略部分......
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
在这里可以看到methods、properties、protocols,这说明对象的方法、属性和协议我们都可以通过这里去获取到,method_array_t、property_array_t、protocol_array_t都是继承list_array_tt。
接下来我们开始获取class_data_bits_t bits,我们能够获取到类的地址,也就是类的首地址,接下来就是确定class_data_bits_t相对于首地址需要偏移多少才能找到他,我们再来看下类的四个属性 isa 8字节,superclass8字节,cache_t cache占有多少呢?
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
union { // 联合体取最大字节和内部字节的整数倍,所以8字节
struct {
explicit_atomic<mask_t> _maybeMask; // 4
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
};
所以cache_t cache占有16字节,所以最终我们需要偏移8 + 8 + 16 = 32字节的位置,就能找到我们的class_data_bits_t。
@interface WTPerson : NSObject
{
@public
short sex;
NSString *nickName;
}
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
+ (void)classMethod;
- (void)testMethod;
@end
上图我们根据地址偏移32位获取到了bits,然后调用bits->data()一步步获取到了person存储的方法列表,我们可以看到person中有5个方法。我们再来看看内部是否真正存的是我们设置的方法吗?这里我们发现$7中返回的是
entsize_list_tt,entsize_list_tt其实是c++的模版,在源码中查找到该结构体我们查到其中有Element& get(uint32_t i)方法,说明我们可以使用这个方法获取我们的methods。我们继续打印,确发现虽然确实得到了可是打印出来的不是我们想要的,不过打印出来了method_t,那我们就继续查看method_t的内部结构,我们都清楚method中存在的重要变量就是SEL和IMP,查询源码时我们发现struct big中存在这两个变量,我们可以继续欢乐的打印了:
贫穷的我使用的是inter的电脑,M1电脑查询过程会有一些小差异,inter使用的是大端存储,M1使用的是小端存储,所以M1电脑无法使用
big()取值,而是应该用small()。
大端:高位字节存放内存的低地址段,低位字节存放内存的高地址段
小端:高位字节存放内存的高地址段,低位字节存放内存的低地址段
如:0x01050814 ,两位16进制数据代表一个字节,需要4个字节进行存储 1111 代表16进制的15
大端:0x100001存储0x01 0x10002存储0x05 0x10003存储0x08 0x10004存储0x14
小端:0x100001存储0x14 0x10002存储0x08 0x10003存储0x05 0x10004存储0x01
两着的存储方式正好相反。另一种不用big,small的通用打印方法是method_t中的getDescription():
同样,我们也可以把所有属性都打印出来:
这里我们也了解到了,属性列表中没有包含成员变量
nickName,方法列表中也没有类方法+ (void)classMethod;
总结
综上所述我们可以得到:
实例对象 isa -> 类对象 isa -> 元类对象 isa -> 根元类 isa -> 根元类本身
元类对象的isa指针不会收到继承影响,使用指向根元类,而元类的父类就是父类的元类,根元类的父类就是NSObject,NSObject的父类是nil。
我们可以拿到类的地址进行偏移32位,获取到类存储信息的地方,然后一步步得到我们想要的属性和对象方法。