当前位置: 首页 > news >正文

「iOS」————消息传递和消息转发

UI学习

  • 消息传递和消息转发
    • 消息传递
    • 消息转发
      • 方法签名


消息传递和消息转发

  • SEL就像是方法的 “名字”,是一个字符串,用于在运行时查找方法。
  • IMP是方法的具体实现,是一个函数指针。
  • _cmd是方法内部的一个参数,代表当前正在执行的方法选择器。

选择子SEL

选择子(Selector)是用于表示方法名的数据类型。它是一个在运行时由编译器生成的唯一的标识符,用于在对象中查找并调用相应的方法。

OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的

IMP

IMP是一个函数指针,它指向方法的实际实现。当运行时系统找到了与选择器相匹配的方法时,它会获取该方法的IMP,然后调用这个函数指针来执行方法的代码。

IMP通常被声明为id返回类型和接受id类型的self和SEL类型的_cmd参数的函数指针。

一般通过SEL来查找方法的IMP

快速查找是针对缓存,慢速查找是查找本类的方法列表和继承链的缓存和方法列表

简短总结:

  • 【快速查找流程】首先,在类的缓存cache中查找指定方法的实现
  • 【慢速查找流程】如果缓存中没有找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找
  • 【动态方法决议】如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod 方法
  • 【消息转发】如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发
  • 如果转发之后也没有,则程序直接报错崩溃unrecognized selector sent to instance

消息传递的流程

  • 首先,RunTime通过obj的isa指针找到其所属的class
  • 接着在这个类的缓存中查找与选择器匹配的方法实现。(即快速查找)
  • 如果缓存中没找到,那就在这个类的方法列表中查找与SEL匹配的IMP。
  • 如果当前类没找到,Runtime会沿着类的继承链往他的superclass中查找,先查找缓存,在查找方法列表,直到根类。
  • 一旦找到这个函数,就会执行他的IMP
  • 如果知道根类都没有找到,则会进行消息转发流程。

消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

快速查找是针对缓存,慢速查找是查找本类的方法列表和继承链的缓存和方法列表

消息传递

快速查找

  1. 首先判断 receiver 是否为 nil 或 tagged pointer,若是则直接返回。
  2. 通过 receiver 的 isa 指针获取类对象,再通过类对象结构体偏移找到 cache_t 结构。
  3. cache_t 结构中包含 buckets(方法缓存表)和 mask(哈希掩码)。
  4. 用 cmd 的哈希值与 mask 做与运算,得到索引 index,定位到 buckets[index]。
  5. 检查 bucket 中的 sel 是否等于 cmd,若相等则命中(hit),返回 imp。
  6. 若不等,则采用线性探测法(index++,循环查找),直到找到空 bucket 或回到起始位置。
  7. 若遍历一圈未找到,则进入 jumpmiss,走慢速查找流程。

慢速查找:进入 _lookUpImpOrForward 函数

  1. 若 cache 未命中,则进入 lookUpImpOrForward。
  2. 检查 cls 是否为有效类对象,否则报错。
  3. 若类未初始化,则先初始化。
  4. 遍历当前类的方法列表查找 cmd,若找到则返回 imp,并写入 cache。
  5. 若未找到,则递归查找父类,直到父类为 nil。
  6. 若最终未找到 imp,则尝试动态方法解析(resolveInstanceMethod)。
  7. 若解析失败,则进入消息转发流程(objc_msgForward)。
  8. 查找过程中每次命中 imp 都会写入 cache 以优化下次查找。

动态决议 resolveMethod_locked

慢速查找没找到IMP时,进入方法动态解析。

  • 首先判断进行解析的是否是元类

  • 如果不是元类,则调用_class_resolveInstanceMethod进行实例方法动态解析

  • 如果是元类,则调用用_class_resolveClassMethod进进行类方法动态解析如过没有找到,则在调用_class_resolveInstanceMethod找实例方法,因为类是元类的实例。

  • 当完成类方法动态解析后,再次查询cls中的imp,如果没有找到,则进行一次对象方法动态解析

  • 最后执行 lookUpImpOrForwardTryCache函数

resolveInstanceMethodresolveClassMethod也称为方法的动态决议。

resolveInstanceMethod方法

  • 针对实例方法调用,在快速-慢速查找均没有找到实例方法的实现时,我们有一次挽救的机会,即尝试一次动态方法决议,由于是实例方法,所以会走到resolveInstanceMethod方法
  • 发送resolveInstanceMethod消息前,需要查找cls中是否有该方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法
    • 如果没有,则直接返回
    • 如果有,则发送resolveInstanceMethod消息
  • 再次慢速查找实例方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找实例方法

注意:此处进入慢速查找中,是查找实例方法动态解析是否实现。

之后调用lookUpImpOrNilTryCache方法,重新返回到方法查找的流程当中去;

lookUpImpOrNilTryCache方法

resolveInstanceMethodresolveClassMethod中都会调用的lookUpImpOrNilTryCache方法

extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{//**这里behavior没传,所以是默认值0**//**behavior | LOOKUP_NIL = 0 | 4 = 4**return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

给参数behavior拼接上LOOKUP_NIL然后调用_lookUpImpTryCache方法

lookUpImpTryCache方法

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{runtimeLock.assertUnlocked();//**判断类是否初始化**if (slowpath(!cls->isInitialized())) {//**没有初始化直接调用lookUpImpOrForward**//**里面针对没初始化的类,有相关处理**return lookUpImpOrForward(inst, sel, cls, behavior);}//**去缓存中,查找sel是否有对应的imp**IMP imp = cache_getImp(cls, sel);//**找到了则跳去done**if (imp != NULL) goto done;//**没找到继续往下走,去共享缓存中查找**
#if CONFIG_USE_PREOPT_CACHESif (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);}
#endif//**没找到对应的imp,调用lookUpImpOrForward方法查找,behavior = 4**//** 4 & 2 = 0 ,所以这次方法查找不会再次进行动态方法决议**//**将_objc_msgForward_impcache缓存起来,方便下次直接返回**if (slowpath(imp == NULL)) {return lookUpImpOrForward(inst, sel, cls, behavior);}done://**命中缓存中,并且sel对应的imp为_objc_msgForward_impcache**//**说明动态方法决议已经执行过,且没有添加imp,则直接返回空**if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {return nil;}//**说明动态方法决议中添加了对应的imp**return imp;
}
  • 首先判断类是否初始化,如果没有初始化则直接调用lookUpImpOrForward,里面有针对没初始化的类进行相应的处理;

  • 然后去缓存中进行方法的快速查找,找到了就去done

  • 缓存中没找到,如果支持共享缓存,则去共享缓存中查找

  • 都没有查找到,则通过慢速方法查找去查找方法,由于behavior 的值发生改变,这次慢速查找不会再次调用动态方法决议

  • 在done流程中,如果已经执行过动态方法决议且并没有添加imp,则缓存中的sel对应imp为_objc_msgForward_impcache,这时直接返回nil。否则返回添加的imp实现。

以上则是这个歌动态决议阶段,如果都没找到实现,则进入消息转发流程:

消息转发

消息转发的方法有三个:

  • 【快速转发】forwardingTargetForSelector
  • 【慢速转发】
    • methodSignatureForSelector
    • forwardInvocation

img

方法签名

方法签名本质上是一个 NSMethodSignature 对象,它封装了以下信息:

  • 返回值类型:例如 intNSString*void
  • 参数数量
  • 每个参数的类型
  • 方法调用的调用约定(Calling Convention)

在底层,这些类型信息通常使用 Type Encodings 来表示,这是一种将类型信息编码为字符串的方式。例如:

  • i 表示 int
  • @ 表示 id
  • : 表示 SEL
  • v 表示 void
http://www.lryc.cn/news/601382.html

相关文章:

  • 携带参数的表单文件上传 axios, SpringBoot
  • 深度解读Go 变量指针
  • [每周一更]-(第152期):Go中的CAS(Compare-And-Swap)锁原理详解
  • iOS安全和逆向系列教程 第20篇:Objective-C运行时机制深度解析与Hook技术
  • 结合Golang语言说明对多线程编程以及 select/epoll等网络模型的使用
  • goland编写go语言导入自定义包出现: package xxx is not in GOROOT (/xxx/xxx) 的解决方案
  • 学习Python中Selenium模块的基本用法(1:简介)
  • Day06–哈希表–242. 有效的字母异位词,349. 两个数组的交集,202. 快乐数,1. 两数之和
  • 仓库管理系统-2-后端之基于继承基类的方式实现增删改查
  • 7.25 C/C++蓝桥杯 |排序算法【下】
  • macOS 安装 Homebrew
  • JavaScript事件(event)对象方法与属性
  • mac配置多版本jdk
  • C#中Visual Studio平台按照OfficeOpenXml步骤
  • Min-Max标准化​ 和 ​Z-score标准化
  • Python队列算法:从基础到高并发系统的核心引擎
  • LeetCode|Day27|70. 爬楼梯|Python刷题笔记
  • Spring Retry 异常重试机制:从入门到生产实践
  • Spring Boot自动配置原理深度解析
  • 适配IE11(通过Babel+core-js转译ES6语法)
  • Flutter 生命周期介绍
  • 几个注册中心的特性
  • 欧拉图与欧拉回路
  • 菜鸟的C#学习(四)
  • windows 10安装oracle(win64_11gR2)
  • 医疗AI语义潜空间分析研究:进展与应用
  • Unity 实时 CPU 使用率监控
  • IP--MGER综合实验报告
  • Linux驱动20 --- FFMPEG视频API
  • 回归预测 | MATLAB实现BiTCN双向时间卷积神经网络多输入单输出回归预测