类其实无非就是研究isa的走位和类的继承关系这两个,先从isa走位开始进入正题。
基础知识(类,元类,根)
类
我们创建的类基本上都继承自NSObject,而NSObject里面有一个默认参数isa;也就是说,继承自NSObject的类里面都会有一个isa。
Meta Class(元类)
在我们平时开发中会用到类方法和实例方法,但是在底层的实现中并没有这种区分,实际上都是通过实例方法去查找的,底层为了区分这两种方法,引入元类的概念,然后将实例方法存储在类中,类方法存储在Meta Class(元类)中。类的isa指向元类。
1、Meta Class(元类)就是类对象所属的类。一个对象所属的类叫做类对象,而一个类对象所属的类就叫做元类。
2、Meta Class(元类)的定义和创建,都是由编译器自动完成。
3、所有的类方法,都存储在Meta Class(元类)中。
根类
根类:在OC中几乎所有的类都继承自NSObject类,NSObject类就是根类,根类的父类为nil
根元类
根元类:即为根类NSObject的isa指向的类
通过 NSobject 跟自定义的 LGPerson 类来查看 isa 的走位图
分析NSobject的isa 走位图
创建一个对象NSObject * obj = [NSObject alloc],通过 lldb 调试一下
看图得出结论:
1.NSObject的对象 object1的isa->NSObject类
2.NSObject的isa->根元类
3.根元类的 isa ->根元类自身
分析自定义的 LGPerson 的 isa 走位图
创建一个对象LGPerson *person = [LGPerson alloc];,通过 lldb 调试一下
截图中未显示全的代码如下
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff80768fe0 0x00007fff80768fe0
0x100008348: 0x00000001005a09e0 0x0002e03500000007
(lldb) p/x 0x00007fff80768fe0 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x00007fff80768fe0
(lldb) po $8
NSObject
(lldb) p/x NSObject.class
(Class) $9 = 0x00007fff80769008 NSObject
(lldb)
元类的isa指向根元类,疑问:为什么不是指向NSbOject?注意NSbOject类地址和根元类类地址不一样,所以指向的是根元类
LGPerson的对象person的isa --> LGPerson类
LGPerson类的isa--> LGPerson类的元类
元类isa--> 根元类
总结
对象的isa指向类对象
类对象isa指向元类
元类isa指向根元类
根元类isa指向根元类(指向自己)
类,元类,根元类的继承关系
已知LGTeacher 继承LGPerson,LGPerson 继承 NSObject
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(LGTeacher.class);//LGTeacher的元类
Class tsuperClass = class_getSuperclass(tMetaClass);//LGTeacher的元类的父类
NSLog(@"teachertMetaClass是%@地址是 - %p tsuperClass是%@ -地址是 %p",tMetaClass,tMetaClass,tsuperClass,tsuperClass);
// LGPerson元类
Class pMetaClass = object_getClass(LGPerson.class);//LGPerson的元类
Class psuperClass = class_getSuperclass(pMetaClass);//LGPerson的元类的父类
NSLog(@"personpMetaClass是%@地址是 - %p psuperClass是%@--地址是%p",pMetaClass,pMetaClass,psuperClass,psuperClass);
Class nMetaClass = object_getClass(NSObject.class);//NSObject的元类
Class nMetaSuperClass = class_getSuperclass(nMetaClass);//NSObject的元类的父类
NSLog(@"NSobjectMetaClass是%@地址是 - %p psuperClass是%@--地址是%p",nMetaClass,nMetaClass,nMetaSuperClass,nMetaSuperClass);
源码分析
NSObject的父类打印的结果是nil。LGTeacher的元类的父类是LGPerson的元类(LGPerson的元类的地址和LGPerson类的地址不一样)。LGPerson的元类的父类是NSObject的元类,NSObject的元类的父类是NSObject(和NSObject类的地址一样)
LgTeacher 继承LgPerson,LgPerson 继承 NSObject,NSObject的父类是nil
LgTeacher元类 继承 LgPerson元类,LgPerson 继承 根元类,根元类继承NSObject
类之间继承流程图
isa的走位图和继承图
苹果官网提供的走位图
补充
1.我们知道LGTeacher继承自类LGPerson,这时候LGTeacher有个对象 t,LGPerson有个对象 p,请问 p 跟 t有什么关系?答案是没有任何关系(千万不能说是继承关系)
2.子类继承自父类,父类继承自根类,根类继承自什么?答案是 nil(根类不是继承与自己)
类的结构分析
通过上篇文章ios 底层原理 02-对象的本质&isa关联类我们知道 isa 是 Class 类型的。Class是objc_class *,objc_class是一个结构体。
通过源码查看
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//这是用在非 OBJC2 上面的
/* Use `Class` instead of `struct objc_class *` */
现在用到的如图所示。
之前源码分析过
objc_class 继承自 objc_object 在搜索 objc_object的时候,需要注意源码里面有 2 个定义 objc_object的地方,这里我们使用的是objc.h里面的(通过 main.cpp 得出的)还有一个在objc-private.h里面。
objc_class 跟 objc_object的关系
1.结构体 objc_class 继承自 objc_object
2. objc_object有一个isa属性,所以 objc_class 也有一个 isa 属性(PS:在代码里面有个注释 // Class ISA;可以验证)
3.NSObject也有isa。因为 nsobject在底层其实就是个 objc_object
类中信息的探索
我们看到上面的图片里还有其他三个成员变量.既然我们知道类的首地址,通过之前说的首地址+偏移量可以获取里面的成员变量的地址,通过地址来取值。但是偏移量需要知道当前变量之前的所有成员变量的大小。
isa结构体指针为 8 字节Class superclass也是结构体指针为 8 字节;cache_t cache是结构体类型的大小,由结构体内成员变量决定class_data_bits_t bits根据前面三个成员变量大小,可以得到bits的地址。
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
所以,需要知道cache_t cache的大小
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //大小为 8
union {
struct {
explicit_atomic<mask_t> _maybeMask;//大小为 4
#if __LP64__
uint16_t _flags;//大小为 2
#endif
uint16_t _occupied;//大小为 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
。。。。。。。。。。。。。。。省略一些源码
cache_t分析
cache_t是结构体类型,有 2 个成员变量_bucketsAndMaybeMask和一个union联合体
_bucketsAndMaybeMask是uintptr_t无符号长整形大小为8字节union联合体里面有两个成员变量_originalPreoptCache跟struct结构体,(之前的知识:联合体的内存大小由成员变量中的最大变量类型决定)_originalPreoptCache是结构体指针大小为8字节struct结构体里面有三个参数_maybeMask;//大小为 4_flags;//大小为 2_occupied;//大小为 2 ,所以结构体是 8 字节cache_t的内存大小为8+8或者是8+4+2+2都是16字节
结论
isa内存地址为首地址superclass内存地址为首地址+0x8cache内存地址为首地址+0x10bits内存地址为首地址+0x20
bits分析
如图所示,
bits.data()应该存储数据,data()的类型是 class_rw_t.查看 class_rw_t的源码就是上面圈出来的.
可以看出,class_rw_t是结构体类型,里面提供了获取方法列表,属性列表,协议列表的方法.
下面继续探究属性,方法,成员变量等等
bits.data()分析
类的属性
先看下类里面的属性,我们在代码里面定义了2 个属性
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
根据上面分析的,寻找这 2 个属性,流程如图
类的方法
流程如下:NSObject.class->metaClass->class_data_bits_t->class_rw_t->method_arrat_t->method_list_t->method_t->big
类方法存储在元类中的方法列表里面实例方法在类中- 编译器自动生成
元类,目的是为了存放类方法
成员变量
流程如下:class_rw_t -> ro() 获取 成员变量列表容器 class_ro_t -> ivars 获取成员变量列表 从成员变量列表中获取成员变量
NSObject.class->metaClass->class_data_bits_t->class_rw_t->class_ro_t->ivar_list_t->ivar_t
协议方法
流程如下:从 class_rw_t -> protocols 获取 协议列表容器 取出 list 取出 ptr 取出 ptr 中的数据 protocol_list_t 这里可以发现,里面的list中存了一个指针,并不是完整的一个数据结构。我们找到了这样的定义:
typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
获取 protocol_t * 读取数据
实例方法
从 class_rw_t -> methods 获取 方法列表容器 取出 list 取出 ptr 取出 ptr 中的数据 method_list_t 获取方法数量 通过c++函数get()与big()单个获取类的实例方法
这里除了我们自己定义的一些实例方法,还看到了一些列属性自动生成的set、get方法,以及析构函数