1.准备工作
我们要研究类,所以首先肯定要需要定义几个类,这里我们就定义两个类,并且他们之间最好有继承关系,方便后续探索。
继承自NSObject的YSHPerson类
![](https://i-blog.csdnimg.cn/blog_migrate/869c436e28ce83550056fd27380a1772.png)
继承自YSHPerson的YSHStudent类
![](https://i-blog.csdnimg.cn/blog_migrate/7abeb94a2cca99954df121e44dcd2c18.png)
在main.m文件中定义两个对象
![](https://i-blog.csdnimg.cn/blog_migrate/faa898e5053aaf9145b61495df105d29.png)
2.元类
什么是元类?
以类作为其实例的类。
元类的定义和创建都是由编译器完成的。
对象的isa指向类,而类其实也是一个对象,我们可以称之为类对象,而类对象的isa则指向了元类。 这么看元类的定义太抽象了,下面我通过lldb结合代码进行分析。
![](https://i-blog.csdnimg.cn/blog_migrate/a0f0cf583d2235f9e59f4bef26bcb6ca.png)
isa走位
由上图分析,我们大致可以得出isa的走位:
对象的isa指向类
类的isa指向元类
元类的isa指向根元类(NSObject)
根元类的isa指向自己
3.继承
下面我们来分析一下继承关系,我们分为两种:
类的继承关系 我们先写一个函数,通过信息打印分析一下。
![](https://i-blog.csdnimg.cn/blog_migrate/a58681a9bd27555681548fe2d206b1d3.png)
通过👇的打印信息,我们可以看出类的继承关系:子类---->父类---->父类的父类---->根类(NSObject)---->nil
![](https://i-blog.csdnimg.cn/blog_migrate/84dacab44cf11b0b6519f0f7c3a0bcf2.png)
2. 元类的继承关系
刚才我们分析了类的继承关系,那我们大胆猜测元类是否也有相应的集成关系呢?老规矩,我们还是先写一个函数,通过信息打印来更加直观的分析。
![](https://i-blog.csdnimg.cn/blog_migrate/005fa6e8447458aa71043e78bc134620.png)
通过👇的打印信息,我们可以看出来元类之间也是存在继承关系的,但是元类的继承链路和类的继承链路还是有不同的。特殊之处在根元类(NSObject)最后又继承了根类(NSObject) 子元类---->父元类---->父元类的父元类---->根元类(NSObject)---->根类(NSObject)---->nil
![](https://i-blog.csdnimg.cn/blog_migrate/c39d9f174244196fea59716a0dfd0b37.png)
4.isa走位&类继承总结
根据上面的探索分析,我们可以得出来那一份肥肠经典的isa&继承走位图
![](https://i-blog.csdnimg.cn/blog_migrate/03b8223e2ee36686977205036e4eb6af.png)
isa走位
实例对象(instance of subclass)的isa指向类(class)。
类对象(class)isa指向元类(meta class)。
元类(meta class)的isa指向根元类(NSObject)(root metal class)。
根元类(NSObject)(root meta class)的isa指向自己本身,形成闭环。 继承关系
类的继承
类(subClass)继承自父类(superClass)。
父类(superClass)继承自根类(NSObject)(rootClass)。
根类(NSObject)继承自nil,这也是为什么NSObject是根类。
元类的继承
子类的元类(metal subClass)继承自父类的元类(metal superClass)。
父类的元类(metal superClass)继承自根元类(root metal class)。
根元类(root metal class)继承自根类(NSObject)(root class)。 特别注意!!!!! 不是说OC没有继承关系吗?此处说的继承关系是对象的继承关系,而非类。类是有继承关系的。
5.类的结构分析
5.1内存偏移
分析类的结构之前,我们需要了解内存偏移,因为我们想要拿到类的内容时,需要通过内存偏移去取。
普通指针
![](https://i-blog.csdnimg.cn/blog_migrate/6d317c87b0a579b9e7a21e7c858519aa.png)
打印结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/ff5f4c1c75c68df7fa1f043defeb4c29.png)
a和b都指向10,但是a和b的地址不一样,地址之间相差4字节,因为a和b都是int类型。
对象指针
![](https://i-blog.csdnimg.cn/blog_migrate/17a60cee2492e1767e862187da1239ce.png)
打印结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/c90010b156a69956837cdc23ffd822dd.png)
p1和p2也是指针,分别指向[YSHPerson alloc]创建的内存地址。 &p1和&p2是指向p1和p2对象指针的地址。
数组指针
![](https://i-blog.csdnimg.cn/blog_migrate/689f300751fbf081edb2520ed4915e82.png)
打印结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/069520b31cf295c88896707188454faf.png)
①&c和&c[0]取出来的都是首地址。 ②&c和&c[1]相差4个字节(地址之间差值,取决于存储的数据类型) ③通过首地址+偏移量可以取出数组中的元素,偏移量也就是我们常说的数组下标。内存中首地址移动的字节数 = 偏移量 * 数据类型字节数
5.2类结构探索
通过objc源码,我们可以大致知道objc_class内部大致有哪些东西,如下图所示: ps:因为源码中有很多方法,对我们用处不大,这里已省略。
![](https://i-blog.csdnimg.cn/blog_migrate/5e7ed173add1214aa296d72ae93ff3c5.png)
①isa:虽然此处注释了,但是objc_class继承自objc_object,所以objc_class肯定也是有isa的,占8字节。 ②superclass:Class类型,本质是一个结构体指针,占8字节。 ③cache:是cache_t类型,通过源码我们可以知道cache_t是结构体类型,结构体类型的内存大小是由其内部的属性来决定的。 ④bits:是class_data_bits_t类型,我们无法获取其具体内存大小,只能通过我们上面说的内存偏移去获取bits。
cache内存大小计算
![](https://i-blog.csdnimg.cn/blog_migrate/566197b4cd7d9801bfe07ed4c06705a5.png)
我们发现cache_t是一个结构体,除去static修饰的属性,其中影响其内存大小的只有👆截图中的两个:_bucketsAndMaybeMask和联合体 我们可以得出cache_t这个结构体的内存大小为8+8=16字节。
获取bits 在通过内存偏移获取bits内容前,我们先看下源码。 我们底层探索的思路就是:大胆猜测、小心验证!!!
我们在源码中发现了一个很有意思的结构体class_rw_t,这里面有几个我们非常熟悉的属性method_array_t property_array_t property_array_t,不管三七二十一,我先认为属性、方法存在这里面,下面👇我们正式开始验证这个大胆的猜测。
属性列表-property_list 我们首先从属性列表开始探索,具体分析如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/d6f1ab8f103f2008a0a3628a618504b5.png)
我们发现property_list里只有属性,没有成员变量。那么成员变量又会存在哪里呢?下面我们一步一步探索。 PS:属性与成员变量的区别就是有没有set/get方法,如果有,则是属性;如果没有,则是成员变量。
方法列表-methods_list 探索方法列表的方式同属性列表,lldb具体步骤如下图所示: 我们会发现在方法列表里只有实例方法和属性的set/get方法,并没有类方法
![](https://i-blog.csdnimg.cn/blog_migrate/66d7cb06b3d9a1527bd303c33d53f25f.png)
成员变量-ivars 刚才分析属性列表时,我们遗留了一个问题,property_list中只有属性,那么成员变量存在哪里了呢? 通过查看class_rw_t的源码发现,其中除了methods、properties、protocols,还有一个ro,通过查看class_ro_t源码,我们发现其有一个ivar_list_t类型的ivars属性。 我们再次做次做出大胆猜测:成员变量是否存储在这个ivar_list_t类型的ivars属性中呢?下面我们就继续小心验证。 下面👇就是lldb步骤:
![](https://i-blog.csdnimg.cn/blog_migrate/ceb48a9cdd9767d53df387e3e189aec1.png)
我们获取的ivars属性,通过打印发现 成员列表中除了sex,还有_name和_familyName。
5.2.1属性&成员变量总结
通过@property声明的属性,存储在类的bits-->data()-->properties()-->list,只包含属性
通过{}声明的成员变量,存储在类的bits-->data()-->ro()-->ivars中,除了成员变量,还包括属性定义的_成员变量
类方法 刚才我们分析methods_list时,也遗留了一个问题,发现类的methods里只有实例方法,并没有类方法,那么类方法又是存储在哪里呢? 在最开始我们分析isa走位的时候,提到了元类的概念,元类是用来存储类的信息的。**那么我们就又可以大胆猜测了:类方法是否就是存储在元类的methods_list里呢?** 下面👇是我们进行lldb验证的步骤:
![](https://i-blog.csdnimg.cn/blog_migrate/6dbecc06dadc1705aa553e0871b2ea3a.png)
经过我们的层层验证,发现我们大胆的猜测都是正确的!!!💪💪💪
5.2.2实例方法&类方法总结
类的实例方法存储在bits-->data()-->methods()-->list,其中既包括实例方法,还包括属性的set/get方法。
类的类方法存储在元类bits-->data()-->methods()-->list,其中只有类方法。
原文链接:https://juejin.cn/post/6975082049975615495
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓