本文主要内容
1.探求ivar的存储方位
2.ro、rw、rwe解析
3.类方法的存储方位
4.关于元类的说明
5.通过runtime的API探求类的数据结构
一、探求ivar的存储方位
上一篇研讨了类的部分底层
,了解到isa的走位图以及类方针和元类方针的承继联系,并且知道类的本质是一个叫作objc_class的结构体
,其间包括了ISA
指针、(类方针的)父类、cache
、bits
等,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
结构体中。
我们来验证类的成员变量是否真的存储在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个成员变量)如下图:
知识小亮点
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批改
,其间分类和本类有必要是非懒加载类
。
分析源码,会判别rw
是否存在rwe
,假设存在rwe
,就会在其间找需求批改的内容(方法、协议等),假设不存在rwe
就会去ro
中找。
三、类方法的存储方位
2个疑问
的另一个疑问是:类方法存在在哪里?
猜想:上一篇文章中发现,类方法并不在类的bits
数据中,那类方法是否会在类的元类的bits
中呢?我们带着疑问进行探求。首要通过类的isa指针指向
找到元类的内存地址,再运用获取bits中方法列表的方法
找到元类中的方法列表即可(具体分析进程此处省掉,如有不清楚的当地,请查看上一篇文章),分析如下图:
知识小亮点
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等内容。
通过runtime中的class_copyIvarList
函数拿到成员变量列表ivars
,遍历即可获取一切的成员变量。
留意⚠️:
class_copyIvarList
方法回来的ivar *
类型,该方法中运用malloc 拓荒了内存空间,而ARC进行内存处理只会处理OC方针,所以需求用free
开释ivars
。
2.获取类的特色
通过runtime中的class_copyPropertyList
函数拿到特色列表properties
,遍历即可获取类的一切特色name、age、height。具体完结如下图:
打印特色类型解析:
闪现如特色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
2.获取实例方法和类方法
(1)实例方法
通过runtime中的class_copyMethodList
函数拿到方法列表methods
,遍历即可获取实例方针的一切方法。具体完结如下图:
打印特色类型解析:
闪现如方法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个长度.
(2)类方法
通过runtime中的class_getInstanceMethod
函数传入元类也能得到类方法的内存地址。为什么?根据我们了解,获取类方法可以运用class_getClassMethod
函数,所以objc底层并没有实例方法和类方法之分
。
查看源码发现:class_getClassMethod
实际上调用class_getInstance Method
函数。
由此进一步说明,苹果规划元类的意图并不是寄存类方法而是为了复用消息机制!!!
3.获取方法的完结imp
通过runtime中的class_getMethodImplementation
函数获取方法的完结。调查发现,此函数既能通过类找到实例方法的完结,也能通过元类找到。为什么呢?
查看class_getMethodImplementation
函数的源码发现,假设找不到方法的完结,会回来_objc_msgForward
来进行消息转发
。
本文总结
1.类的ro中存储成员变量,实例方法、特色、协议等也存储在类方针中;
2.类方法存储在元类中;
3.元类的规划是为了复用消息机制,并非为了寄存类方法;
4.objc底层并没有实例方法和类方法之分。
有任何问题,欢迎各位议论指出!觉得博主写的还不错的费事点个赞喽