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

「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.xdnf.cn/news/16549.html

相关文章:

  • K8S 九 安全认证 TLS
  • 深入理解现代前端开发中的 <script type=“module“> 与构建工具实践
  • Orange的运维学习日记--13.Linux服务管理
  • OpenLayers 综合案例-点位聚合
  • 【通识】线性代数(Linear Algebra)
  • 深度学习在计算机视觉中的应用:对象检测
  • 从 .NET Framework 到 .NET 8:跨平台融合史诗与生态演进全景
  • 设计模式(十一)结构型:外观模式详解
  • ESP32步进电机控制实战:从原理到代码实现
  • JAVA秋招学习指南
  • 【Java实例】服务器IP一站式管理
  • linux 部署 flink 1.15.1 并提交作业
  • ios UIAppearance 协议
  • 元宇宙背景下治理模式:自治的乌托邦
  • 移植pbrt中的并行化到ray trace in weeks中
  • 268. 丢失的数字
  • RocksDB跳表MemTable优化揭秘
  • Java 集合进阶:从 Collection 接口到迭代器的实战指南
  • Containerd简介
  • 栈算法之【有效括号】
  • mybatis-plus从入门到入土(三):持久层接口之IService
  • Day 22: 复习
  • OTG原理讲解
  • 进制间的映射关系
  • 【RHCSA 问答题】第 12 章 安装和更新软件包
  • WorkManager vs Flow 适用场景分析
  • CSS变量与Houdini自定义属性:解锁样式编程新维度
  • [硬件电路-94]:模拟器件 - 信号耦合,让被放大信号与静态工作点的直流偏置信号完美的融合
  • 慧星云新增大模型服务:多款大模型轻松调用
  • 编程语言Java——核心技术篇(四)集合类详解