对象的isa指针指向哪里?
OC对象: instance
class
meta-class
instcane
的isa
指针指向calss
对象
class
的isa
指针指向meta-class
对象
meta-class
的isa指向 Root class(基类NSObject)
Root class(基类NSObject)
的isa
指向自己
Root class(基类NSObject)
的superClass
指针指向该类(基类NSObject)的class
对象.
基类NSObject没有父类.
创建
LDPerson
和 NSObject
的一个分类
@interface LDPerson : NSObject
+ (void)test;
@end
@implementation LDPerson
+ (void)test{
NSLog(@"+[LDPerson test] - %p",self);
}
@end
//NSObject (Test) 分类
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
+ (void)test{
NSLog(@"+[NSObject test] - %p",self);
}
@end
将LDPerson
中的test
方法实现去掉,即LDPerson中没有test
方法的实现,通过[LDPerson test]
调用时,就会通过superclass
指针去父类NSObject
中需找方法实现,打印如下:
再将NSObject
中的类方法实现去掉,添加一个对象方法-(void)test
并实现.代码如下:
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
//+ (void)test{
// NSLog(@"+[NSObject test] - %p",self);
//}
- (void)test{
NSLog(@"-[NSObject test] - %p",self);
}
现在的情况是LDPerson
和NSObject
中都没有+(void)test
方法.当LDPerson
和NSObject
两个class
对象调用+(void)test
方法时,会调用NSObject class
对象的实例方法(instance method)
.
分析:
LDPerson
调用+(void)test
方法:[LDPerson test]
;
1.直接拿到LDPerson
的class对象的isa
指针.因为调用的是类方法,类方法存放在meta-class
中.
2.通过isa
指针需找到LDPerson
的meta-class
对象,遍历meta-class
的方法列表,发现没有+(void)test
方法的实现.
3.通过LDPerson
的superclass
指针找到父类NSObject
的meta-class
,遍历NSObject
的meta-class
方法列表.发现没有发现+(void)test
方法的实现.
4.通过NSObject
的meta-class
的superclass
指针找到NSObject
的class
对象.因为NSObject(根类Rootclass)
的 meta-class
的superclass
指向NSObject
的class
对象.遍历NSObject
的class
的方法列表,就找到了-(void)test
方法.调用该方法,结束调用流程.
5.如果还没有找到该方法的实现,就会进入 动态方法解析
消息转发
阶段,如果还没有处理就会报方法找不到的错误.
isa指针真实地址:
在arm64
架构之前,isa
就是一个普通的指针,存储着class
,meta-Class
对象的内存地址,从arm64
架构开始,对isa(64位)进行了优化,变成了一个共用体(union)结构.用位域技术来存储更多的信息.其中有一个成员变量shiftcls 用33位存储
class,
meta-class对象的地址
.isa
中还会存储着弱引用(weakly_refrenced)
等信息.
isa
指针的最后三位都是0,打印地址会发现最后一位都是0 或8(16进制的8),8 在2进制是1000
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
类对象``class,meta-class
的本质是 struct objc-class
的结构,
Sturct objec_class{
Class isa;
Class superClass
cache_t cache //方法缓存
class_ data _ bits _t bits //用于获取具体类的信息
}
struct objc-class
里面的bits
& FAST_DATA_MASK
会得到一个结构体 class_rw_t
struct calss_rw_t {
uint32_t flags;
uint32 version;
const class_ro_t * ro
method_list_t * methods;//方法列表 (原类和分类的方法)
property_list_t * properties;//属性列表
const protocol_list_t * protocols;//协议列表
Class firstSubclass;
Class nextSiblingClass;
char * demangleName;
}
class_rw_t
结构体里面有一个const class_ro_t *
类型的ro
成员变量.ro
也是一个结构体
struct class_ro_t{
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//instance对象占用的内存空间
#ifdef LP64
uint32_t reserved;
#endif
const uint8_t * ivarlayout;
const char * name; // 类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成员变量列表
const uint8_t * weakIvarLayout;
property_list_t * baseProperties
}
clss_rw_t
里面的methods
,properties
,protocols
是二维数组,是可读可写的,包含了类的初始化内容(创建类时的方法 属性等信息)和分类的内容
struct class_rw_t {
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
}
method_array_t methods
是一个二维数组,第一层数组里面包含 method_list_t
(第二层数组),method_list_t
里面包含method_t
类型数据
property_array_t
和 protocol_array_t
的结构与method_list_t里面的结构类似
method_list_t
的一维数组中是有序的,分类的方法会放在数组的前面,原有的方法会放在数组的后面.解释了调用方法的时候会优先寻找分类方法,会覆盖原有的方法.
struct class_rw_t
结构中的 const class_ro_t ro
,class_ro_t
里面的baseMethodList
,baseProtocols
,ivars
,baseProperties
是一维数组,是只读的,包含了类的初始化内容:方法 成员变量 属性等(注意不包含 分类信息)
struct class_ro_t {
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
property_list_t * baseProperties;
}
method_list_t * baseMethodList
中包含的是method_t
类型的数据,注意ro_t
结构体中的method_list_t * baseMethodList
一维数组是只读的,不能修改.
class metaclass 创建的底层运行逻辑:
编写代码运行后,开始类的方法,成员变量 属性 协议等信息都存放在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
c语言字符串用%s
打印,OC字符串用%@
打印方法缓存
cache_t cache : Class
内部结构有个方法缓存(cache_t)
,用散列表来缓存曾经调用过的方法,可以提高方法的查找速度.
LDPerson *person = [[LDPerson alloc] init]`
[person test]
person instance对象调用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
采用散列表 hashTable
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
减一操作,如果都被占用进行扩容操作)并将目标存到该索引位置,就是哈希表的核心原理!