本文主要内容

1.探求ivar的存储方位
2.ro、rw、rwe解析
3.类方法的存储方位
4.关于元类的说明
5.通过runtime的API探求类的数据结构

一、探求ivar的存储方位

上一篇研讨了类的部分底层,了解到isa的走位图以及类方针和元类方针的承继联系,并且知道类的本质是一个叫作objc_class的结构体,其间包括了ISA指针、(类方针的)父类、cachebits等,bits中存储了类的特色实例方法协议等内容,而这些内容都放在一个叫class_rw_t的结构体中。同时还留下2个疑问,第1个疑问是:成员变量并没有在bits中特色列表中展示。现在我们来研讨类的成员变量究竟存储在什么方位? 通过分析并查看objc4-838.1源码发现,class_rw_t结构体中有1个叫作ro的结构体,回来class_ro_t结构体类型的数据,而class_ro_t结构体中包括ivars,即代表成员变量,也就是说类的成员变量就存储在class_ro_t结构体中。

iOS底层原理之类的底层探求(下)

iOS底层原理之类的底层探求(下)

我们来验证类的成员变量是否真的存储在class_ro_t结构体中。方法同上一篇中获取bits里面的methodlists和propertylist类似。读取bits的回来class_data_bits_t数据内容,通过data函数读取得到class_rw_t结构体类型数据,通过*取值,再调用ro函数获取到class_ro_t结构体类型数据,其间ivars就是成员变量!调用ivars即可回来ovar_list_t类型数据,此时同前面特色和方法的获取相同了。通过get函数获取其间每个ivar类型元素的数据,从而找到累的成员变量(包括5个成员变量)如下图:

iOS底层原理之类的底层探求(下)

iOS底层原理之类的底层探求(下)

知识小亮点
A:为何真实的成员变量放在类中,而成员变量的值却放在实例方针中(前面现已知道,实例方针包括isa指针和成员变量的值)? 
Q:类的本质是一个结构体,这个结构体相当于一个模版,这个模版中有成员变量、特色、方法、协议等内存,实例方针就是根据类的模版生成的,在创立实例方针时,不同的实例方针成员变量的值是不相同的,所以就把不相同的成员变量的值寄存在不同的实例方针中。

二.ro、rw、rwe解析

ro是在编译时生成,当类进行编译时,类的特色实例方法协议等就会存在于ro结构体中,它是一块纯净的、只读(read only)的内存空间,不允许被批改,即clean memory。rw是在运转时生成的,类一经运用ro就会变成rw,即rw会把ro中的内容”剪切”到rw中,可读可写(read&write)。而runtime提供了动态为类增加方法和特色的API,这些方法和特色存在于只读不允许批改的ro中,假设想要批改方法和特色(一般批改比例为10%左右),需求把ro中的内容”仿制”到rw中,但这样就会存在两份ro,增加内存消耗。所以苹果通过class_rw_ext_t(rwe)结构体来处理这10%左右的批改,这些批改主要指分类或许runtime API批改,其间分类和本类有必要是非懒加载类

iOS底层原理之类的底层探求(下)

iOS底层原理之类的底层探求(下)

分析源码,会判别rw是否存在rwe,假设存在rwe,就会在其间找需求批改的内容(方法、协议等),假设不存在rwe就会去ro中找。

iOS底层原理之类的底层探求(下)

三、类方法的存储方位

2个疑问的另一个疑问是:类方法存在在哪里?
猜想:上一篇文章中发现,类方法并不在类的bits数据中,那类方法是否会在类的元类的bits中呢?我们带着疑问进行探求。首要通过类的isa指针指向找到元类的内存地址,再运用获取bits中方法列表的方法找到元类中的方法列表即可(具体分析进程此处省掉,如有不清楚的当地,请查看上一篇文章),分析如下图:

iOS底层原理之类的底层探求(下)

知识小亮点
ro存在磁盘中,运用时内存加载。只需APP运转rw就会一直存在,APP杀死才会开释。

结论类方法确实存储在元类中!

四、关于元类的说明

上一篇文章中第二部分还有一个疑问:什么是元类?为什么要引出元类呢?也就是说苹果为什么要规划这个元类呢?
这是为了**复用消息机制**,用同一套消息机制。在OC中调用方法,如[HGPerson alloc],在苹果体系中实际上就是给HGPerson发送某条消息,调用方法编译时就会编译为包括2个参数的函数objc_msgSend(消息接收者 isa,消息方法名),通过这个函数根据消息接收者的isa指针找到该方法的完结,如消息接收者是实例方针,就会到实例方针isa指针指向的类方针中找该方法的完结,假设消息接收者是类方针,就会到类方针isa指针指向的元类方针中找该方法的完结(所以类方法和实例方法可同名)。
假设没有元类,只用2个参数无法找到方法的完结,需求批改为:objc_msgSend(消息接收者,消息方法名,判别实例方针/类方针,判别实例方法/类方法),而消息的发送最重要的是快速,假设增加上述这些判别结构会影响发送功率!运用当时的消息机制只通过isa指针可以很快找到方法的完结,实例方针存储成员变量的值,类方针存储实例方针的方法,元类方针存储类方针的方法,也就是单一职责的准则,大大增加消息发送的功率,同时维护同一个消息机制(objc_msgSend函数)也更便利。

五、通过runtime的API探求类的数据结构

1.获取类的成员变量
成员变量为ivar_t类型的结构体,其间包括name、type、size等内容。

iOS底层原理之类的底层探求(下)
通过runtime中的class_copyIvarList函数拿到成员变量列表ivars,遍历即可获取一切的成员变量。

iOS底层原理之类的底层探求(下)

留意⚠️:class_copyIvarList方法回来的ivar *类型,该方法中运用malloc 拓荒了内存空间,而ARC进行内存处理只会处理OC方针,所以需求用free开释ivars

iOS底层原理之类的底层探求(下)

2.获取类的特色
通过runtime中的class_copyPropertyList函数拿到特色列表properties,遍历即可获取类的一切特色name、age、height。具体完结如下图:

iOS底层原理之类的底层探求(下)

打印特色类型解析:
闪现如特色name“T@'NSString',C,N,V_name”。其间"T"代表类型,后边加"@‘NSString’即为字符串类型,"C"代表copy,"N"代表"nonatomic","V_name"代表成员变量_name.
再如特色age”Ti,N,V_age**“,"Ti"整体代表int类型,"N"代表"nonatomic","V_age"代表成员变量_age.

特色类型编码说明官网地址:Declared property type encodings

iOS底层原理之类的底层探求(下)
iOS底层原理之类的底层探求(下)

2.获取实例方法和类方法
(1)实例方法 通过runtime中的class_copyMethodList函数拿到方法列表methods,遍历即可获取实例方针的一切方法。具体完结如下图:

iOS底层原理之类的底层探求(下)

打印特色类型解析:
闪现如方法name类型“@16@0:8”.其间"@"代表回来值为方针类型,"16"代表方法参数的长度,第2"@"代表方法的接收者,8个长度,从0-7;":"代表方法名,8个长度,从8-15,所以一共16个长度.
再如特色setHeight“v24@0:8q16”,"v"代表回来值为void类型,"24"代表方法参数的长度,第2"@"代表方法的接收者,8个长度,从0-7;":"代表方法名,8个长度,从8-15;还有个long类型的参数height,8个长度,从16-23,所以一共24个长度.

iOS底层原理之类的底层探求(下)

(2)类方法
通过runtime中的class_getInstanceMethod函数传入元类也能得到类方法的内存地址。为什么?根据我们了解,获取类方法可以运用class_getClassMethod函数,所以objc底层并没有实例方法和类方法之分

iOS底层原理之类的底层探求(下)
查看源码发现:class_getClassMethod实际上调用class_getInstance Method函数。 由此进一步说明,苹果规划元类的意图并不是寄存类方法而是为了复用消息机制!!!

iOS底层原理之类的底层探求(下)

3.获取方法的完结imp
通过runtime中的class_getMethodImplementation函数获取方法的完结。调查发现,此函数既能通过类找到实例方法的完结,也能通过元类找到。为什么呢?

iOS底层原理之类的底层探求(下)
查看class_getMethodImplementation函数的源码发现,假设找不到方法的完结,会回来_objc_msgForward来进行消息转发

iOS底层原理之类的底层探求(下)

本文总结

1.类的ro中存储成员变量,实例方法、特色、协议等也存储在类方针中;
2.类方法存储在元类中;
3.元类的规划是为了复用消息机制,并非为了寄存类方法;
4.objc底层并没有实例方法和类方法之分。

有任何问题,欢迎各位议论指出!觉得博主写的还不错的费事点个赞喽