美文网首页
Runtime & 消息转发机制

Runtime & 消息转发机制

作者: 小李不木 | 来源:发表于2021-08-05 17:11 被阅读0次

怎么理解OC是动态语言,Runtime又是什么?

静态语言:如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。

动态语言:把一些决定性的工作从编译阶段推迟到运行时阶段。如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。

Runtime 可以OC代码编译转化为运行时代码,通过消息机制决定函数调用方式,是 OC 面向对象和动态机制的基石。

面向对象编程的三大特性是:封装、继承、多态

1.封装:抽象出数据类型和数据操作构成一个整体。需要类中有:成员变量、方法

2.继承:类之间的父子关系 ,OC中的类中存在一个isa指针和super_class指针,通过他们建立起了类之间的父子关系。

3. 多态:指针变量指向的具体类型,方法调用在运行时才能确定。

多态的三个必要条件是:继承、重写、父类指针可以指向子类对象。

OC是一门动态语言:

1. 可以在运行时新增方法(使用class_addMethod为类新增方法)

2. 可以改变类的结构(使用class_replaceMethod替换方法的实现等)

3. 运行时检查类型(运行时多态,id类型)

OC与Runtime的交互划分三种层次,交互程度从低到高排序:

1. OC源代码

2. NSObject方法

3. Runtime 函数

类的runtime 底层实现:

class 结构体

class_rw_t: 提供了运行时对类拓展的能力。内容可以在运行时被动态修改的,可以说运行时对类的拓展大都是存储在这里的。(比如:分类)

 class_ro_t :存储的大多是类在编译时就已经确定的信息。

注意:二者都存有类的方法、属性(成员变量)、协议等信息,不过存储它们的列表实现方式不同。

class_rw_t 中使用的 method_array_t, property_array_t, protocol_array_t 都继承自 list_array_tt<Element, List>,   它可以不断扩张(类似于二位数组,数组里面可以存放数组,后续可把分类添加的方法,属性等加入进来),可以存储 list 指针。

存储的内容有三种:1. 空   2. 一个 entsize_list_tt 指针   3. entsize_list_tt 指针数组

支持category:类的 method_list 会先插入链表,然后再头插category的method_list。

注意:头插法:category的method_list 放在数组的最前面。方法调用时是顺序查找,category中重写原类方法会覆盖原类。如果有多个分类,最晚编译的category中的方法会被执行。

分类详解: 分类了解 --传送门

realizeClass:realizeClass 处理后的类才是『真正的』类,调用时不能对类做写操作。

类初始化之前,objc_class->data() 返回的指针指向 class_ro_t 结构体。等 static Class realizeClass(Class cls) 静态方法在类第一次初始化时被调用,它会开辟 class_rw_t 的空间,并将 class_ro_t 指针赋值给 class_rw_t->ro。

其他字段大概含义:

1. ivars: 用于存放所有的成员变量和属性信息

 使用场景:我们在字典转换成模型的时候需要用到这个列表找到属性的名称,去取字典中的值,KVC赋值,或者直接Runtime赋值

2. methodLists: 用于存放对象的所有成员方法。

3. selector:方法选择器,编译时,会依据类名,方法名字、参数序列等,生成一个唯一的整型标识 (Int类型的地址),用来区分方法的 ID,selector 方法选择器名称不区分 +,-方法,这个ID是 SEL 类型,是个映射到方法的C字符串

注意

1. 不同类中相同名字的方法对应的方法选择器是相同的。

2.即使是同一个类中,方法名相同而变量类型不同也会导致它们具有相同的方法选择器。因此 Objc 中方法命名有时会带上参数类型来进行区分。

3. 同一个类,方法名不能重复。不同的类,可以重复

获取SEL有 3 种方法:

1.  OC中,使用@selector(“方法名字符串”) 或者  使用NSSelectorFromString(“方法名字符串”)      

2.  Runtime方法,使用sel_registerName(“方法名字符串”)

 IMP:函数指针,指向方法的实现。

注意:避开消息发送,直接获取方法的地址并调用会更高效。 NSObject 类中有methodForSelector: 实例方法。可以用它来获取某个方法选择器对应的IMP地址。

方法结构体

当objc_msgSend找到方法的实现时,将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,  任何方法默认都有两个隐式参数:是在代码被编译时被插入实现中的。

1.  self:接收消息的对象。在当前方法中使用  self关键字, 引用实例本身, 

2:_cmd:方法选择器。  _cmd 和 imp 是一一对应的

Rutime消息发送基本原理

OC的方法调用都是类似 [receiver  selector] 的形式,其实是一个运行时消息发送过程。

消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现。

编译阶段:确定了要向哪个接收者发送message消息,编译器转化:

1.不带参数的方法被编译为:objc_msgSend(receiver,selector)

2.带参数的方法被编译为:objc_msgSend(recevier,selector,org1,org2,…)

注意:编译器会根据情况在 objc_msgSend, objc_msgSend_stret,  objc_msgSendSuper, 或 objc_msgSendSuper_stret  四个方法中选择一个来调用。如果消息是传递给父类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。

在 i386 平台处理返回类型为浮点数的消息时,需要用到  objc_msgSend_fpret 函数来进行处理,这是因为返回类型为浮点数的函数对应的 ABI(Application Binary Interface) 与返回整型的函数的 ABI 不兼容。此时objc_msgSend 不再适用。不过在 PPC 或 PPC64 平台是不需要麻烦它的。 

PS:带  “Super”  的是消息传递给父类;“stret”可分为“  st”+“ret”  两部分,分别代表“struct”和“return”;“fpret”就是“fp”+“ret”,分别代表“floating-point”和“return”。

运行时阶段:

1. 检测 selector 是不是需要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会retain, release 这些函数了。

2. 检测target 是不是nil 对象。ObjC 的特性是允许对一个 nil对象执行任何一个方法不会 Crash,因为会被忽略掉。如果传递给 objc_msgSend 的 self 参数是 nil,该函数不会执行有意义的操作,直接返回。

3. 满足上面两个条件后,通过 isa 指针找到它的 class ; 在类中查找 IMP,先从 cache 里面找,若找到就跳到对应的函数去执行。如果在cache里找不到就去类的 方法列表methodLists 里面找。

4. 如果找不到,就到顺着类的 superclass 指针去它父类的cache 和 methodLists方法列表里找,直到找到NSObject类为止,若找到就跳到对应的函数去执行。

5. 如果还找不到,Runtime就提供了三种方法来处理:动态方法解析消息接受者重定向消息重定向,如果还不能处理,就carsh 。 

注意: 在找到 method 方法后,把方法 的 method_name 作为key ,method_imp 作为value 给存起来。当再次收到这个消息的时候,可以直接在objc_cache 里找到,避免去遍历objc_method_list。

objc_cache 作用 :每次发消息都需要遍历一次 objc_method_list  并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。

参考⚠️⚠️⚠️:isa 指针传送门

三次机会:动态方法解析, 消息接受者重定向, 消息重定向

最后3次机会

一: 动态方法解析(Dynamic Method Resolution)

如果从本类到父类一层层的找也没有找到对应方法的话,就会走消息转发。

当通过 cache 和方法列表都没有找到方法时,Runtime 提供了 一次动态添加方法实现的机会,主要用到的方法如下:

动态添加方法 实现例子

在消息转发前会先走本类的方法 +resolveInstanceMethod:(处理找不到的实例方法)或+resolveClassMethod:(处理找不到的类方法)。在这个方法里面可以使用class_addMethod 函数向实例添加方法,使得消息发送能够正常进行。

1. 如果正确添加了函数, 那运行时系统就会重新启动一次消息发送的过程。如果没有正确的添加函数的实现,即使返回YES ,也会崩溃. 返回的YES,或者NO ,无实质意义。

参考:消息转发第一步resolveInstanceMethod返回YES or NO?_chenyingSunny的专栏-CSDN博客

2. 如果resolve方法返回 NO ,就会移到下一步,消息转发(Forwarding)的阶段forwardingTargetForSelector。

二:消息接收者重定向

Runtime 提供把这个消息转发给其他对象的机会。可以保证程序的继续执行。

重写列子

通过 forwardingTargetForSelector 可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是非nil,非self,系统会将运行的消息转发给这个对象执行。否则进入下一阶段,消息的重定向。

forwardingTargetForSelector 方法,返回一个指定的接收者

1. 返回nil,走转发流程第三步消息重定向

2. 返回非nil对象,走返回对象的消息发送流程,本次消息转发至此结束。 如果返回的对象可处理该方法,哪怕是他自己没有该方法但是父类有,则ok(其实就是消息发送流程)

3.  返回的对象无法处理该方法,接下来走返回对象的消息转发流程

三:消息重定向

Runtime 系统会通过 forwardInvocation 方法消息通知该对象,给予此次消息最后一次寻找IMP(可以被执行)的机会。

1. 每个对象都从 NSObject 类中继承了forwardInvocation 方法,

2. NSObject中forwardInvocation 方法只是简单的调用了doesNotRecongnizeSelector 方法,提示错误。

3. 重写 methodSignatureForSelector 和 forwardInvocation 方法:对不能处理的消息做一些默认处理避免崩溃,也可以将消息转发给其他对象来处理。

在forwardInvocation 消息发送前,Runtime系统会向对象发送methodSignatureForSelector消息,并取到返回的方法签名用于生成NSInvocation对象。

anInvocation :参数中的selector为导致crash的方法,target为导致crash的对象

实现消息转发

1. methodSignatureForSelector如果返回nil,直接crash

2. methodSignatureForSelector 返回只要是非nil且是NSMethodSignature类型的任何值都可以

4. forwardInvocation方法可以啥都不处理,或者做任何不会出问题的事,至此本次消息转发结束,也不会crash。

总结:

forwardInvocation方法: 像一个不能识别的消息的分发中心,可将消息同时转发给任意多个对象。也可将所有的消息都发送给同一个接收对象。或者对不同的消息提供同样的响应。

1. forwardingTargetForSelector 仅支持一个对象的返回,消息只能被转发给一个对象

2.  简单的”吃掉“某些消息,没有响应也没有错误。 理论上可以重载 doesNotRecognizeSelector 函数,重写使其不抛出异常(不调用super实现)。但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。

3. forwardInvocation 能够修改消息的内容,用于实现更加强大的功能。

使用场景:

向 nil 对象发送消息则不会产生崩溃

1. - ( NSMethodSignature * ) methodSignatureForSelector: ( SEL ) aSelector 

2.  - ( void ) forwardInvocation: ( NSInvocation * ) anInvocation 

重写 这两个方法将没能力处理消息的方法签名转发给 nil 对象则不会产生崩溃

使用场景:runtime 使用场景

参考:

Objective-C Runtime | yulingtianxia's blog 详解class 类结构体中 rw  & ro 等字段的具体含义

Runtime-iOS运行时基础篇 - 简书 👍👍👍 

Runtime_唐姐让改名的博客-CSDN博客_runtime     时间上更新,class 结构体有改变,详细讲解ISA结构体类型。

Runtime整理

深入浅出Runtime (一) 什么是Runtime? 定义? - 简书  里面有具体的class 类结构体的底层代码

iOS self和super底层实现原理 - 简书  self和super 的区别 

相关文章

  • runtime系列文章总结

    《iOS Runtime详解(消息机制,类元对象,缓存机制,消息转发)》《消息转发机制与Aspects源码解析》《...

  • Runtime 的应用

    前面我们说到:Runtime 消息传递机制Runtime 消息转发机制Runtime 交换方法今天我们来谈谈Run...

  • Effective Objective-C读后笔记(2)

    11、runtime消息转发机制 runtime的消息转发流程图消息转发 消息转发的示例实现 这里也给大家推荐一篇...

  • iOS - Runtime - 概念和方法交换

    runtime的概述runtime的相关概念runtime消息机制消息传递动态方法解析消息转发runtime的作用...

  • iOS消息转发机制

    消息转发机制: 消息转发机制是相对于消息传递机制而言的。 1、消息(传递)机制 RunTime简称运行时。就是系统...

  • Runtime知识点整理1

    OC消息机制?消息转发机制流程?什么是Runtime?什么场景下使用? ==============巴拉巴拉......

  • runtime消息转发机制

    消息转发机制基本上分为三个步骤: 1. 动态方法解析 2. 备用接收者 3. 完整转发 我们可以通过控制这三个步骤...

  • Runtime 消息转发机制

    在编译期,向对象或者类对象发送了其无法解读的消息并不会报错,因为在运行期可以继续向类和元类(metaClass)中...

  • runtime:消息转发机制

    若想令类能理解某条消息,我们必须以程序码实现出对应的方法才行。但是,在编译期向类发送了无法解读的消息并不会报错,因...

  • runtime消息转发机制

    Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展...

网友评论

      本文标题:Runtime & 消息转发机制

      本文链接:https://www.haomeiwen.com/subject/mrusbctx.html