当前位置: 首页 > 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.dtcms.com/a/301362.html

相关文章:

  • 26.删除有序数组中的重复项
  • MyBatis-Plus高效开发实战
  • 内存管理和垃圾收集-02: 操作系统如何管理内存?
  • Linux驱动开发笔记(五)——设备树(中)——节点的标准属性
  • 益莱储:明智地投资测试仪器
  • S7-1500 与 S7-1200 存储区域保持性设置特点详解
  • 电子板原理功能区解析与PlantUML图示
  • 3,Windows11安装docker保姆级教程
  • 轻量化多模态文档处理利器SmolDocling:技术原理与场景落地引言:文档智能处理的范式革命
  • 数据结构基础内容(第六篇:二叉搜索与平衡二叉树)
  • MySQL锁机制与MVCC原理剖析
  • 直播带货工具About v1.5.10 免费版
  • GEO优化实战:如何在DeepSeek、豆包等AI平台抢占推荐位?
  • MOE架构详解:原理、应用与PyTorch实现
  • 计算圆周率(π)代码实现【c++】
  • Java中排序规则详解
  • cJSON在STM32单片机上使用遇到解析数据失败问题
  • 计算柱状图中最大的矩形【单调栈】
  • Dify 本地化部署深度解析与实战指南
  • 蜣螂优化算法的华丽转身:基于Streamlit的MSIDBO算法可视化平台
  • 【ESP32设备通信】-W5500与ESP32 /ESP32 S3集成
  • MySQL - 性能优化
  • Java面试实战:电商高并发与分布式事务处理
  • maven optional 功能详解
  • Java进阶7:Junit单元测试
  • 数据结构基础内容(第九篇:最短路径)
  • OpenCv中的 KNN 算法实现手写数字的识别
  • 电子电路设计学习
  • git回退版本教程
  • Java validation