Class的结构
class_rw_t
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
class_ro_t
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
底层运行逻辑:编写代码运行后,开始类的方法,成员变量 属性 协议等信息都存放在 const class_ro_t中,运行过程中,会将信息整合,动态创建 class_rw_t,然后会将 class_ro_t中的内容(类的原始信息:方法 属性 成员变量 协议信息) 和 分类的方法 属性 协议 成员变量的信息 存储到 class_rw_t中.并通过数组进行排序,分类方法放在数组的前端,原类信息放在数组的后端.运行初始 objc-class中的 bits是指向 class_ro_t的,bits中的data取值是从class_ro_t中获得,而后创建 class_rw_t,class_rw_t中的 class_ro_t从初始的 class_ro_t中取值,class_rw_t初始化完成后,修改 objc_class中的bits指针指向class_rw_t
method_t
-
method_t是对方法\函数的封装
- IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull,SEL _Nonnull,...);
- SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
- 可以通过@selector()和sel_registerName()获得
- 可以通过sel_getName()和NSStringFromSelector()转成字符串
- 不同类中相同名字的方法,所对应的方法选择器是相同的
typedef struct objc_selector *SEL;
types包含了函数返回值、参数编码的字符串
返回值 | 参数1 | 参数2 | ... | 参数n |
---|
Type Encoding
iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码
方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度.
缓存查找:
-objc-cache.mm
- bucket_t * cache_t::find(cache_key_t k, id receiver)
cache_t cache : Class
内部结构有个方法缓存(cache_t)
,用散列表来缓存曾经调用过的方法,可以提高方法的查找速度.
LDPerson *person = [[LDPerson alloc] init]
[person test]
消息机制:
思路:test
方法为对象方法,存储在class
对象当中.所以首先会通过instance
的isa
指针查找到LDPerson
的class
对象,然后去cache
方法缓存列表中查找是否有该方法的缓存,如果有直接调用,如果没有进行下面的操作
然后找到class
对象中的 methodList
(方法列表)进行遍历查找该方法,如果查找到该方法,调用该方法,并添加到方法缓存cache
中,方便下次调用,省略再次调用遍历方法列表(二维数组)的操作,如果在该class
对象中没有找到该方法,会通过该class
对象的superClasss
指针查找到class
对象的父类 superClass
,再查找到superClass
的cache
方法缓存列表,如果缓存中没有该方法,则会通过bits --> class_rw_t --->methodList
,进行遍历操作,沿构造链一直向上查找,查找到最底层的baseClass
类,如果还没找到该方法,就会抛出异常,该方法找不到的错误.
注意:类初始化后,第一次调用某方法时就会将方法添加到缓存列表cache
中,二次及以后调用时,会优先去cache
中查找是否有该方法的缓存,如果有直接调用,如果没有重复上述操作.子类对象第一次调用父类方法时,会通过isa
指针向上查找,查找到父类方法后会将该父类方法添加到自己的方法缓存列表中,下次调用时,就不需要superClass
指针查找父类方法了!
cache散列表实现原理:
通过struct bucket_t
结构体中的两个成员变量:cache_key_t _key(SEL作为key)
和imp _imp
(函数指针,指向函数的地址)例如:
LDPerson *person = [[LDPerson alloc] init]
[person test]
@selector(test) & _mask
通过SEL
和结构体cache_t
中的_mask
做与运算,得到一个值,这个值就是该方法test
在cache_t
结构体数组 _buckets(方法缓存列表)
中的索引.当test
方法第一次被调用时,通过sel&_mask
得到该方法索引,并将该方法存储到方法缓存数组的索引位置.当先次调用该方法时,会有优先去方法缓存列表中寻找_mask
,通过该方法的SEL&_mask
得到该方法索引位置,然后通过该索引去_buckets
列表中去查找该方法,如果为空,则遍历LDPerson
的class
对象的方法列表(加入_caches
缓存列表,如果没有沿构造链向上寻找...).如果多个方法的SEL&_mask
得到的索引相等,即该索引位置已经存储过方法,则将得到的索引位置减1,作为该方法的索引位置.如果减一后的索引位置还是被占用,最终会让索引值等于Mask
,当_buckets
缓存列表数组需要扩容时,每次扩容都是当前容量的二倍.扩容时会充值_mask
的值!因为_mask
被重置,方法SEL&_mask
的索引位置发生变化,已经无法正确获取到该方法的缓存索引,所以_buckets
会清空缓存,重新开始计算索引位置并缓存方法.
哈希表的核心原理: f(key) == index
,通过一个函数算法(求余或与运算 或其他运算),传入一个作为查找的KEY
的得到一个索引位置,如果该索引位置被占用,则可进行其他运算,直到得到一个不被占用的位置.(Apple通过减一操作获取新索引,如果减一到索引0的位置还是被占用,则设置索引位置为_mask
,如果还是被占用则进行_mask
减一操作,如果都被占用进行扩容操作)并将目标存到该索引位置,就是哈希表的核心原理!