一、初识Runtime数据结构

Runtime的数据结构主要包括objc_object、objc_class、isa 指针、method_t对象。下面我们一起学习一下吧。

一、objc_object

我们实际中使用的都是id类型的对象,id 类型的对象在runtime 中对应的就是objc_object结构体,它主要包括一下几个成员变量:
(1)isa_t (共用体)
(2)关于isa的相关操作通过objc_object结构体获取它的isa类对象,以及通过类对象的isa获取元类对象的实例以及方法。
(3)弱引用相关的方法(标记一个对象是否曾经有过弱引用指针)
(4)关于关联对象相关的方法。(为对象设置了关联属性,而这些关联属性的方法也体现在objc_object结构体中)
(5)内存管理方法的实现(retain\release@autoReleasePool)

如下图所示:

objc_object结构示意图

二、objc_class

我们在OC语言中所使用到的Class对应于runtime中objc_class结构体。objc_class继承与objc_object. objc_class包含的成员变量是:
(1)Class superclass指针,它指向对象也是Class类型,如果说是类对象那么它指向元类对象。如果是实例对象它指向父类对象。
(2)cache_t cache 它表达了方法缓存的结构。
(3)class_data_bits bits(类的变量、属性、方法)

如图所示:


objc_class结构示意图

三、isa指针

  1. isa 指针的含义?
    在C++中是一个共用体,被定义成了isa_t.它实际是32或64位0或者1的数字。它分为指针型的isa(整体内容代表Class的地址)以及非指针型的isa(部分内容代表Class的地址,为了节约内存开销)。
  2. isa 指针的指向
  • 对于对象,其指向类对象。
    OC中实例对象是id 类型,在runtime中就是objc_object类型其含有一个isa,该isa会指向父类对象。
  • 对于类对象,其指向元类对象.
    Class-----isa----->MetaClass
    因为Class是类对象,因为它继承于objc_object。也拥有一个isa,而它的isa指针是指向元类对象的。
isa指针指向

3.方法的调用过程

  • 如果调用的是实例方法时,实际上是实例的isa指针到它的类对象的方法中查找,
  • 如果调用的是类方法,类对象的isa 指针会到它的元类对象的方法中进行查找.

四、cache_t cache

  1. cache 指针的含义?
  • 用于快速查找方法执行函数。 (也就是说,当调用方法时,如果缓存中有,那么就不会到它的方法列表中查找。)
  1. 是可增量扩展的哈希表结构。(可增量扩展体现在:当我们的结构,存储量增大时,他也会扩大自己的内存空间,来存储。使用哈希表存储,主要是提高查找效率。)
  2. 局部性原理的最佳应用。(局部性原理,在我们使用时,也许就那么几个方法,如果将其存储下来,下次的在调用的时候命中率更高一些,从而提高程序的运行速度。)
  3. cache_t 的数据机构
    可以看作是一个包含若干个bucket_t 结构体的数组。bucket_t 主要包含两个成员变量:key与IMP(key对应OC 中的selector,IMP是对应的函数指针,或者是函数体). 如果有一个Key,可以通过哈希算法,定位到相应的bucket_t,然后从bucket_t中提取到IMP的具体函数实现,从而完成方法的调用。

五、class_data_bits bits(类的变量、属性、方法)

  • class_data_bits_t主要是对class_rw_t的封装。
  • class_rw_t 代表了类相关的读写信息、对class_ro_t的封装。
  • class_ro_t 代表了类相关的只读信息
  1. class_rw_t 主要包含有class_ro_t\protocols\properties\methods.
    而protocols\properties\methods时间是一个继承于list_array_tt 的二维数组。methods数组中的元素是一个方法列表,子数组中的元素都是方法列表中的method_t 数据结构。如图所示:
class_rw_t结构图
  1. class_ro_t 数据结构
    包括:name/ivars/protocols/properties/methodList.
    而ivars/protocols/properties/methodList 是一维数组。methodList的元素:method_t.

六、method_t方法列表

首先,我们一起回顾一下有关函数的知识。
函数的四要素:函数声明名、参数、返回值、函数体。
method_t 是函数结构体,是对函数四要素的封装。
method_t 中的成员有:SEL name、const char * types、无类型的IMP imp.
他们分别对应函数四要素是:函数名、参数与返回值、函数体。
如下图所示:


方法结构体.png

struct method_t 返回值和参数如何表示的?涉及到了Type Encodings技术,使用到了const char * types的字符指针,该指针的数据结构,是这样的,
首位并且仅有一位,是返回值,接着是objc_messageSned的两个固定参数,最后是,我们自定的参数。比如说:
-(void)aMessage;
它的const char * types 就可以表示为:v@: 其中 v 代表 返回值为void类型、@ 代表 id 类型 、 : 代表选择器SEL.

我们所调用的方法或者消息传递到达runtime时,都会转化成objc_messageSend:这样的函数调用。而@和: 就是这个函数的两个固定的参数,并且第一个参数必须是id类型的对象(消息的接收者)。

(4)整体的数据结构如何组合在一起的??

如下图所示:


Runtime的数据结构.png

七、对象、类对象、元类对象

  • 类对象是存储实例方法列表信息
  • 元类对象是存储类的类方法列表信息
消息传递流程.png
  1. 消息传递流程:

使用---> 表示 isa ,使用 ====> 表示superclass

根类的类对象对应于OC 中NSObject,它的父类是nil,也就是没有父类。它的子类为superclass以及子类的子类subclass.首先我们有一个实例变量(instance of Subclass)----> Subclass类----> Meta of Subclass

三者之间的关系:实例对象可以通过isa指针找到自己的类对象,而父类的类对象又可以通过isa 指针找到自己的元类对象。

对于任何一个元类对象的isa 指针,都指向根元类对象,包括根元类对象的isa 指针也指向它本身

对于它的superclass 指针指向传递,如下所述:

类对像之间的指向:
Subclass(class) ===> Superclass(class) ===>Rootclass(class) ===> nil

元类对象之间的指向:
Subclass(meta) ===> Superclass(meta) ===>Rootclass(meta) ===>Rootclass(class) ===> nil

注意:根元类对象的superclass指针指向根类对象,最后指向nil 。也就是说,当我们调用类方法的时候,我们会沿着上面的路径查找,最终会到根元类对象的类方法列表中查找,如果查找不到,这时再去根类对象的实例方法列表中查找同名的实例方法,仍然没有查到的话,就直接返回nil抛出异常(常见的:unrecognized selector sent to class 0x10cc4ace0')

消息传递流程的具体描述

  • 调用实例方法
    当我们调用实例方法时,首先实例对象会根据自己的isa指针,找到对应的类对象,在该类对象的实例方法列表中遍历查找同名的方法实现,如果没有查到,类对象再根据自己的superclass指针找到其父类对象,再在父类对象的实例方法列表,遍历查找同名的方法实现,还是没有找到的话,父类对象再根据superclass指针找到其根类对象,在进行查找。如果还没有找到,就进行消息转发流程。

  • 调用类方法
    首先,类对象会根据isa指针找到其元类对象,在元类对象的类方法列表中遍历查找同名方法,如果没有查到,就会根据其superclass 指针,到父元类,在到根源类-->根类-->最后到nil.

我们来看一下这样一个面试题

面试问题

输出结果是什么? 全部都是Phone .
具体分析:

  • [self class] 消息的接收者:当前类对象
  • [super class] 消息的接收者:当前对象??

两者的消息的接收对象都是当前对象,只是super class 是从当前对象的父类对象的方法列表中,查找,最终都会到NSObject中查到class方法的实现。

消息传递在编译过后都会转化成相应的方法调用:

void objc_msgSend(void/id self ,SEL op,.../);

[self class] ----> objc_msgSend(self,@selector(class));

void objc_msgSendSuper(void/struct objc_super * super ,SEL op,.../);super是编译器关键字,编译器编译后,会将super解析成objc_super结构体指针,该结构体的成员变量就是receiver,receiver就是当前对象。

struct objc_super{ _unsafe_unretained id receiver;}
[super class] ----> objc_msgSendSuper(self,@selector(class));

消息传递的流程图:


消息传递的流程图

描述:我们调用方法的流程
首先,会从方法缓存cache_t中查找,如果查找到,就调用,就完成了一次消息传递。如果在缓存中没有查找到,就会到当前类的方法列表中的查找,如果还是没有找到就到其父类(逐级父类)查找,直到根类对象,如果还没有查到,就进行消息转发。

  1. 缓存查找流程(哈希查找)

例如:给定的SEL ,查找对应bucket_t 的 IMP(方法实现).

详细说:就是根据给定的SEL,通过缓存策略或者函数,查找到对应的映射出bucket_t在数组中的位置, 然后,提取出对应的IMP方法实现。通过给定的值,经过哈希算法的 f(key) = key & mask,
这个f(key) 就是bucket_t 在数组中的索引位置。


哈希查找流程.png
  1. 当前类中的查找
  • 对于已经排序好的列表,采用二分法查找算法查找方法对应的执行函数
  • 对于没有排好序的列表,采用一般遍历查找方法对应执行函数。
  1. 父类逐级查找
    如图所示:


    父类逐级查找

首先根据当前类的superclass 指针,找到对应的父类,然后在父类的缓存列表中查找如果查找到了,就结束本次的父类逐级查找,如果没有找到就去遍历父类的方法列表。如果查找到了,就返回给调用方,如果没有查找到,就到父类的父类,直到到NSObject中,如果还未找到就返回nil.

八、消息转发流程(实例方法)

对于实例方法的转发:首先系统会回调resolveInstanceMethod:---参数:SEL 返回值:BOOL,要不要解决当前方法的实现。类方法----->返回是YES,当前消息已经处理,如果为NO,系统会给我我们第二次处理这条消息,会调用(id)forwardingTargetForSelector:告诉系统这个消息由谁处理,转发对象是谁。如果我们给定了一个转发目标的话,系统将这条消息返回给我们指定的转发对象,同时结束当前的消息转发。如果没有给出转发目标(返回nil),系统会再次给我们处理这条消息的机会,调用methodSignatureForSelector: 返回值是对象,它对返回值类型以及参数个数的封装,此时如果我们返回了方法签名,系统会调用forwardInvocation:如果能处理这条消息,则转发流程结束。如果methodSignatureForSelector返回nil或者forwardInvocation,被标记为消息无法处理。

实例方法消息转发流程
类方法消息转发流程.png

具体流程印证,可参照RuntimeObject

九、 Method Swizzling 方法混淆

具体使用方法,可参照RuntimeObject

十、动态添加方法

十一、动态方法解析

  1. @dynamic
    它所修饰的属性的setter、getter方法是在运行时添加的。
  • 动态运行时语言将函数决议推迟到了运行时。
    就是说在运行时,我们调用属性的setter、getter方法的时候再回创建setter、getter方法。
  • 编译时语音在编译期进行函数决议。
    就是说,在编译的时候,就确定了一个函数名的对应的实现体,我们时不能进行修改的。

十二、Runtime实战

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

推荐阅读更多精彩内容