isa 和superClass 指针

对象的isa指针指向哪里?

OC对象: instance class meta-class
instcaneisa指针指向calss对象
classisa指针指向meta-class对象
meta-class的isa指向 Root class(基类NSObject)
Root class(基类NSObject)isa指向自己
Root class(基类NSObject)superClass指针指向该类(基类NSObject)的class对象.
基类NSObject没有父类.

Snip20180824_14.png

创建LDPersonNSObject的一个分类

@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
image.png

LDPerson中的test方法实现去掉,即LDPerson中没有test方法的实现,通过[LDPerson test]调用时,就会通过superclass指针去父类NSObject中需找方法实现,打印如下:

image.png

再将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);
}

现在的情况是LDPersonNSObject中都没有+(void)test方法.当LDPersonNSObject两个class对象调用+(void)test方法时,会调用NSObject class对象的实例方法(instance method).

image.png

分析:

LDPerson调用+(void)test方法:[LDPerson test];

1.直接拿到LDPerson的class对象的isa指针.因为调用的是类方法,类方法存放在meta-class中.
2.通过isa指针需找到LDPersonmeta-class对象,遍历meta-class的方法列表,发现没有+(void)test方法的实现.
3.通过LDPersonsuperclass指针找到父类NSObjectmeta-class,遍历NSObjectmeta-class方法列表.发现没有发现+(void)test方法的实现.
4.通过NSObjectmeta-classsuperclass指针找到NSObjectclass对象.因为NSObject(根类Rootclass)meta-classsuperclass指向NSObjectclass对象.遍历NSObjectclass的方法列表,就找到了-(void)test方法.调用该方法,结束调用流程.
5.如果还没有找到该方法的实现,就会进入 动态方法解析 消息转发阶段,如果还没有处理就会报方法找不到的错误.

isa指针真实地址:

arm64架构之前,isa就是一个普通的指针,存储着class,meta-Class对象的内存地址,从arm64架构开始,对isa(64位)进行了优化,变成了一个共用体(union)结构.用位域技术来存储更多的信息.其中有一个成员变量shiftcls 用33位存储class,meta-class对象的地址.isa中还会存储着弱引用(weakly_refrenced)等信息.

image.png

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

}

image.png

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_tprotocol_array_t的结构与method_list_t里面的结构类似

image.png

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

image.png

method_t c语言字符串用%s 打印,OC字符串用%@打印
image.png

方法缓存

cache_t cache : Class内部结构有个方法缓存(cache_t),用散列表来缓存曾经调用过的方法,可以提高方法的查找速度.

LDPerson *person = [[LDPerson alloc] init]`
[person test]

person instance对象调用test方法的过程:

test方法为对象方法,存储在class对象当中.所以首先会通过instanceisa指针查找到LDPersonclass对象,然后去cache方法缓存列表中查找是否有该方法的缓存,如果有直接调用,如果没有进行下面的操作

然后找到class对象中的 methodList(方法列表)进行遍历查找该方法,如果查找到该方法,调用该方法,并添加到方法缓存cache中,方便下次调用,省略再次调用遍历方法列表(二维数组)的操作,如果在该class对象中没有找到该方法,会通过该class对象的superClasss指针查找到class对象的父类 superClass,再查找到superClasscache方法缓存列表,如果缓存中没有该方法,则会通过bits --> class_rw_t --->methodList,进行遍历操作,沿构造链一直向上查找,查找到最底层的baseClass类,如果还没找到该方法,就会抛出异常,该方法找不到的错误.

注意:类初始化后,第一次调用某方法时就会将方法添加到缓存列表cache中,二次及以后调用时,会优先去cache中查找是否有该方法的缓存,如果有直接调用,如果没有重复上述操作.子类对象第一次调用父类方法时,会通过isa指针向上查找,查找到父类方法后会将该父类方法添加到自己的方法缓存列表中,下次调用时,就不需要superClass指针查找父类方法了!
cache采用散列表 hashTable

image.png

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做与运算,得到一个值,这个值就是该方法testcache_t结构体数组_buckets(方法缓存列表)中的索引.当test方法第一次被调用时,通过sel&_mask得到该方法索引,并将该方法存储到方法缓存数组的索引位置.当再次调用该方法时,会有优先去方法缓存列表中寻找_mask,通过该方法的SEL&_mask得到该方法索引位置,然后通过该索引去_buckets列表中去查找该方法,如果为空,则遍历LDPersonclass对象的方法列表(加入_caches缓存列表,如果没有沿构造链向上寻找...).如果多个方法的SEL&_mask得到的索引相等,即该索引位置已经存储过方法,则将得到的索引位置减1,作为该方法的索引位置.如果减一后的索引位置还是被占用,最终会让索引值等于Mask,当_buckets缓存列表数组需要扩容时,每次扩容都是当前容量的二倍.扩容时会重置_mask的值!因为_mask被重置,方法SEL&_mask的索引位置发生变化,已经无法正确获取到该方法的缓存索引,所以_buckets会清空缓存,重新开始计算索引位置并缓存方法.

哈希表的核心原理:

f(key) == index,通过一个函数算法(求余或与运算 或其他运算),传入一个作为查找的KEY得到一个索引位置,如果该索引位置被占用,则可进行其他运算,直到得到一个不被占用的位置.(Apple通过减一操作获取新索引,如果减一到索引0的位置还是被占用,则设置索引位置为_mask,如果还是被占用则进行_mask减一操作,如果都被占用进行扩容操作)并将目标存到该索引位置,就是哈希表的核心原理!

image.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容