前言
上篇文章分析了isa,并分析了它的结构,分析和验证了它是如何和类关联起来的,这片文章主要分析的是类的结构以及对象-类-元类-根元类之间的走位.
一. isa的指向以及类之间的关系
准备,创建2个类MRPerson和MRTeacher,MRTeacher类继承于MRPerson.在MRPerson定义两个变量和两个方法.(成员变量/属性)(实例方法和类方法)
@interface MRPerson : NSObject{
///成员变量
NSStrin *hobby;
}
///属性
@property (nonatomic,copy) NSString *name;
///实例方法
-(void) sayHello;
///类方法
+(void)sayBye;
@end
在mian函数中,创建这两个类,并对齐内存结构进行LLDB调试
获取类的内存信息
打印类的isa指向的地址
如上的操作步骤:**person**的isa(0x001d80010000228d)指向的是**MRPeson**类,**MRPeson**类的isa(0x0000000100002260)指向的还是**MRPeson**类,第二个**MRPeson**类的isa(0x00000001003330f0)指向的是**NSobject**
根据调试过程,我们产生了一个疑问:为什么图中的调试过程中出现了2个MRPerson和多次打印NSObject的isa指针指向的还是NSObject?
-
两个打印都是
**MRPerson**的根本原因就是因为元类导致的 -
多次打印
**NSObject**,是因为根元类的isa指向自身导致的
元类说明:
万事万物皆对象,
**实例对象**的isa指针指向**类**,**类**它也是一个对象,可以称之为**类对象**,那么问题来了,类对象的isa指针指向那? 苹果爸爸就提出了一个**元类**的概念,用来接受**类对象**的isa指针
**元类是系统提供的**,它的创建是由**编译器自动完成的**,所以我们感受不到.
**元类**是**类对象**的**类**,元类存储的是**类对象**的**方法**
**元类**本身是**没有名称的**,由于与**类**相**关联**,所以使用了**同类名一样的名称**
isa总结
从图中可以看出
**对象**的**isa**指向**类**(也可称为**类对象**)**类对象**的**isa**指向**元类****元类**的**isa**指向**根元类**,即**NSObject****根元类**的**isa**指向 它**自己**
- 由此就引出了
**“cooci老师”**的著名的isa走位图
说明 1.实线代表继承关系,虚线代表isa指向关系
isa的走向有以下几点说明:
-
实例对象(Instance of Subclass)的isa指向类(class) -
类对象(class)isa指向元类(Meta class) -
元类(Meta class)的isa指向根元类(Root metal class) -
根元类(Root metal class)的isa指向它自己本身,形成闭环,这里的根元类就是NSObject
subclass(即继承关系)的走向也有以下几点说明:
-
类之间 的继承关系:-
类(subClass)继承自父类(superClass) -
父类(superClass)继承自根类(RootClass),此时的根类是指NSObject -
根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有
-
-
元类也存在继承,元类之间的继承关系如下:-
子类的元类(metal SubClass)继承自父类的元类(metal SuperClass) -
父类的元类(metal SuperClass)继承自根元类(Root metal Class -
根元类(Root metal Class)继承于根类(Root class),此时的根类是指NSObject
-
【注意】:
实例对象之间没有继承关系,只有类之间有继承关系
举例:
isa 走位
- teacher的isa走位链:
teacher(子类对象) --> MRTeacher (子类)--> MRTeacher(子元类) --> NSObject(根元类) --> NSObject(跟根元类,即自己) - person的isa走位图:
person(对象) --> MRPerson (类)--> MRPerson(元类) --> NSObject(根元类) --> NSObject(根元类,即自己
二. 类的结构分析
在文章《ios对象原理探索三》源码初步查看了类的相关结构,以及知道了类实际上是一个结构体.在跟进过程中我们看到了两个结构体**objc_class**和**objc_object**下面就具体探索下这两个结构体
objc_object & objc_class
在源码中搜索:
上图源码可以看出objc_class继承于objc_object,进入objc_objec_t_源码**:**
总结:
**objc_class**继承于**objc_object**,**objc_class**中有一个**公用的isa**,所以所有的对象都**继承于objc_object**,万物皆来源于**objc_object**
**objc_class、objc_object、isa、object、NSObject**等的整体的关系,如下图所示
探索类信息中都有哪些内容
类的结构中有4部分内容
isa指针(共有属性)superclass指向父类的指针cache缓存bits属性结构体(class_rw_t * plus custom rr/alloc flags)创建
类的属性存储的位置就是我们着重分析bits结构体
知识点:
内存偏移量:众所周知,内存的存储是顺序存储的,我们知道了内存的首地址,知道了相对于内存首地址数据首地址的大小,我们可以拿到任何存储在内存的数据,这个相对于内存首地址的大小就是内存的偏移量.
例如:
解释:
那么根据这个原理,就可以得出有关**bits**的内容
struct objc_class : objc_object {
// Class ISA; //8字节
Class superclass; //Class 类型 8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略,未贴出
}
获取cache的大小
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
#if __LP64__
uint16_t _flags; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
cache的大小为16字节,所以有上述计算可知,想要获取bits的中的内容,只需通过类的首地址平移32字节即可
以下是通过lldb命令调试的过程
通过class_rw_t提供的方法,继续探索 bits中的属性列表
以下是lldb 探索的过程图示
上图可看出$6存储的是一个property_array_t的数组,继续获取里面的值
咋报错了??why?明明我定义的时候定义了一个属性和一个成员变量,怎么只用一个属性,成员变量呢?
由此可得出property_list 中只有属性,没有成员变量,属性与成员变量的区别就是有没有set、get方法,如果有,则是属性,如果没有,则是成员变量。
类的方法列表
方法和类的属性基本相同,我只书写下LLDB调试的内容,如下图:
获取过程:
打印方法结果:
怎么是**4**个方法?我写了两个方法**sayHello**和**sayBay**,为什么是四个方法,sayBay也没有打印,怎么回事?sayBay存储在哪?
由上图可看出:@property属性,由系统自动生成set和get方法.类方法不存在这个类的方法列表中
三.总结
-
本篇文章主要分析了
**isa和类,元类,根元类,根类之间的关系**,**bits如何获取**,着重分析了新的api下**类的结构**,**属性列表**,**方法列表存储**的位置. -
**遗留的问题****成员变量**为什么没有找到,它存储在哪?**类方法**为什么不在类的方法列表中?它到底存储在什么地方?
知识点比较多,流程梳理比较耗时,做个笔记方便复习....