【iOS】方法与消息底层分析

目录

前言

方法的本质

向不同对象发送消息

发送实例方法

发送类方法

对象调用方法 实际执行是父类

向父类发送类方法

消息查找流程

开始查找

快速查找流程

慢速查找流程

动态方法决议

应用场景

优化方案

消息转发机制

快速转发流程

应用场景

慢速转发流程

应用场景


前言

在OC底层中,方法的调用实质上是通过消息的发送实现的,这篇文章我们来看一看消息的发送是怎么样的

方法的本质

方法的本质就是通过objc_msgSend发送消息,有两个参数,第一个是id类型,表示消息接受者,第二个,表示方法编号。

向不同对象发送消息

发送实例方法

消息接收者是实例对象

发送类方法

本质上是向类对象发送消息

objc_getClass得到的是类对象

对象调用方法 实际执行是父类

Runtime中提供了一个接口处理这种情况:父类中实现了该方法,而子类没有实现该方法,子类对象调用方法,会执行父类中实现(符合继承的特性)

这个接口是objc_msgSendSuper,使用时还需要用到objc_super结构体,并给结构体赋值(receiver、super_class)

该结构体中receiver表示接收消息的实例对象,super_class表示父类类对象,根据这个赋值

可以看到这两种方式都是执行父类的实现,因此可以推断:方法调用首先在类中查找,如果找不到就到父类中查找

向父类发送类方法

上面向父类发送实例方法时,receiver表示实例对象,super_class表示父类类对象。而如果向父类发送类方法,reciever表示类对象,super_class表示父类元类对象

消息查找流程

消息查找的流程就是通过上层的sel发送消息objc_msgSend找到底层具体imp的实现的过程,objc_msgSend是用汇编写的而不是用C语言

开始查找

在开始objc_msgSend之后

  1. 首先会判断消息接受者是否为空,为空就直接返回

  2. 然后会判断是否为小对象,也就是是否为tagged_pointers

  3. 之后取对象中的isa存到寄存器p13中,根据isa进行mask地址偏移来得到对应的上级对象(类、元类)

取得了上级对象之后,就可以开始快速查找流程了,也就是在缓存中找imp的过程

快速查找流程

  1. 首先通过类的首地址偏移16字节找到cache的地址(cache离首地址16字节,isa占8字节,superclass占8字节),cache高16位存mask,低48位存buckets

  2. 然后从cache中分别取出buckets和mask,根据mask通过哈希算法算出哈希下标,根据哈希下标和bukets首地址来得到对应的bucket,bucket中存放着imp和sel

  3. 那么怎么确定找到的imp和sel就是要找的那个呢?主要是通过两层循环:

    1. 第一层循环:比较bucket中的sel和objc_msgSend中第二个参数_cmd是否相等:如果相等,就直接跳转到CacheHit,即缓存命中,返回imp;如果不相等,有三种情况:

      1. 一种是一直找不到,就直接跳转到CheckMiss,因为参数$0是normal,会跳转到__objc_msgSend_uncached,看英文就能明白意思就是没找到,这时就会进入慢速查找流程

      2. 第二种是如果获取到的bucket是第一个元素,那么就手动把它设置为最后一个元素,然后进行第二层循环

      3. 如果当前bucket不是第一个元素,那就继续当前的循环

    2. 第二层循环:和第一层循环基本相同,只是如果bucket还是等于buckets中第一个元素,就直接跳转到JumpMiss,此时也会跳转到没找到__objc_msgSend_uncached,进入慢速查找

慢速查找流程

慢速查找的过程分为汇编和C两个部分,这里我们不纠结汇编部分,汇编最后调用的是lookUpImpOrForward,这是一个C实现的函数

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{// 定义的消息转发const IMP forward_imp = (IMP)_objc_msgForward_impcache;IMP imp = nil;Class curClass;
​runtimeLock.assertUnlocked();
​if (slowpath(!cls->isInitialized())) {// The first message sent to a class is often +new or +alloc, or +self// which goes through objc_opt_* or various optimized entry points.//// However, the class isn't realized/initialized yet at this point,// and the optimized entry points fall down through objc_msgSend,// which ends up here.//// We really want to avoid caching these, as it can cause IMP caches// to be made with a single entry forever.//// Note that this check is racy as several threads might try to// message a given class for the first time at the same time,// in which case we might cache anyway.behavior |= LOOKUP_NOCACHE;}
​// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.
​// runtimeLock is held during method search to make// method-lookup + cache-fill atomic with respect to method addition.// Otherwise, a category could be added but ignored indefinitely because// the cache was re-filled with the old value after the cache flush on// behalf of the category.
​//加锁,目的是保证读取的线程安全runtimeLock.lock();
​// We don't want people to be able to craft a binary blob that looks like// a class but really isn't one and do a CFI attack.//// To make these harder we want to make sure this is a class that was// either built into the binary or legitimately registered through// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.//判断是否是一个已知的类:判断当前类是否是已经被认可的类,即已经加载的类checkIsKnownClass(cls);
​//判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链,方法后续的循环cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);// runtimeLock may have been dropped but is now locked againruntimeLock.assertLocked();curClass = cls;
​// The code used to lookup the class's cache again right after// we take the lock but for the vast majority of the cases// evidence shows this is a miss most of the time, hence a time loss.//// The only codepath calling into this without having performed some// kind of cache lookup is class_getInstanceMethod().//----查找类的缓存// unreasonableClassCount -- 表示类的迭代的上限//(猜测这里递归的原因是attempts在第一次循环时作了减一操作,然后再次循环时,仍在上限的范围内,所以可以继续递归)for (unsigned attempts = unreasonableClassCount();;) {if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHESimp = cache_getImp(curClass, sel);if (imp) goto done_unlock;curClass = curClass->cache.preoptFallbackClass();
#endif} else {// curClass method list.//---当前类方法列表(采用二分查找算法),如果找到,则返回,将方法缓存到cache中Method meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {imp = meth->imp(false);goto done;}//当前类 = 当前类的父类,并判断父类是否为nilif (slowpath((curClass = curClass->getSuperclass()) == nil)) {// No implementation found, and method resolver didn't help.// Use forwarding.//--未找到方法实现,方法解析器也不行,使用转发imp = forward_imp;break;}}
​// Halt if there is a cycle in the superclass chain.// 如果父类链中存在循环,则停止if (slowpath(--attempts == 0)) {_objc_fatal("Memory corruption in class list.");}
​// Superclass cache.// --父类缓存imp = cache_getImp(curClass, sel);if (slowpath(imp == forward_imp)) {// Found a forward:: entry in a superclass.// Stop searching, but don't cache yet; call method// resolver for this class first.// 如果在父类中找到了forward,则停止查找,且不缓存,首先调用此类的方法解析器break;}if (fastpath(imp)) {// Found the method in a superclass. Cache it in this class.//如果在父类中,找到了此方法,将其存储到cache中goto done;}}
​// No implementation found. Try method resolver once.//没有找到方法实现,尝试一次方法解析
​if (slowpath(behavior & LOOKUP_RESOLVER)) {//动态方法决议的控制条件,表示流程只走一次behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}
​done:if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHESwhile (cls->cache.isConstantOptimizedCache(/* strict */true)) {cls = cls->cache.preoptFallbackClass();}
#endif//存储到缓存log_and_fill_cache(cls, imp, sel, inst, curClass);}done_unlock://解锁runtimeLock.unlock();if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {return nil;}return imp;
}

上面是慢速查找的源码,用自然语言来表述就是:

  1. 首先进行一次快速查找,也就是在cache缓存中查找,找到就直接返回imp,没找到就继续

  2. 先判断cls是否是已知类,如果不是就报错;再判断类是否实现,如果没实现需要先实现,这个时候实现的目的是为了确定它的父类链,ro以及rw等,方便之后数据读取和查找;还要判断是否初始化,没有就初始化

  3. 接下来进入for循环,沿着类或元类的继承链进行查找:

    1. 对于当前cls,在方法列表中使用二分查找进行查找,如果找到就进入cache写入流程并返回imp,如果没找到就返回nil

    2. 当前cls赋值为父类,如果父类为nil,imp = 消息转发,并终止递归,开始判断是否执行过动态方法解析

    3. 如果父类链中存在循环就报错

    4. 在父类中查找时,会先在父类缓存中查找,再在方法列表中查找

  4. 判断是否执行过动态方法解析,如果没有就执行动态方法解析,执行过一次的话就走消息转发流程

在二分查找过程中,如果找到的与key的value值相等,需要先排除分类方法

在进行完快速查找和慢速查找的流程之后,会进入动态方法决议和消息转发流程

动态方法决议

在查找流程没找到方法时,有一次机会补救就是动态方法决议,以实例方法为例,程序会走到resolveInstanceMethod方法:

用自然语言描述如下:

  1. 在发送resolveInstanceMethod消息前,先查找cls中有没有这个方法的实现,也就是通过lookUpImpOrNil方法进入lookUpImpOrForward慢速查找流程找这个方法:

    1. 如果没找到就直接返回

    2. 如果找到了就发送resolveInstanceMethod消息

  2. 再慢速查找实例方法的实现,又进行一次慢速查找

应用场景

使用动态方法决议可以解决一些方法未实现的报错,重写resolveInstanceMethod类方法并在其中将其指向其他方法的实现,比如有一个say666没实现,但是实现了sayMaster方法

类方法同理,将方法名改为resolveClassMethod即可

优化方案

在上面的场景中,我们需要对每一个类的方法进行重写,并且我们又知道慢速方法查找路径最后都会走到根类,因此我们可以为NSObjct添加分类来统一处理

消息转发机制

如果前面的过程都没找到该方法,那我也是没招了(bushi),那就会进行消息转发流程,消息转发流程分为快速转发和慢速转发,如果方法没有实现而崩溃报错,在崩溃之前会调用两遍动态方法决议,两遍快速转发,两遍慢速转发

快速转发流程

forwardingTargetForSelector在源码中只有声明,但是我们可以从帮助文档中看到有关于它的解释:

  • 该方法的返回对象是执行sel的新对象,也就是自己处理不了会将消息转发给别的对象进行相关方法的处理,但是不能返回self,否则会一直找不到

  • 该方法的效率较高,如果不实现,会走到forwardInvocation:方法进行处理

  • 底层会调用objc_msgSend(forwardingTarget, sel, ...);来实现消息的发送

  • 被转发消息的接受者参数、返回值等应和原方法相同

应用场景

比如TCJPerson没实现的方法,转发给实现了的TCJStudent

也可以直接调用父类的该方法,如果没找到的话会直接报错

慢速转发流程

methodSignatureForSelector慢速查找流程同样在帮助文档中寻找,可以发现forwardInvocationmethodSignatureForSelector必须同时存在

底层会通过方法签名生成一个NSInvocation,作为参数传递使用,接着查找可以响应NSInvocation中编码的消息的对象,找到后使用anInvocation将消息发送给该对象,并且anInvocation保存结果,运行时系统将提取结果并传递给原始发送者

应用场景

慢速转发的流程就是methodSignatureForSelector提供一个方法签名,然后forwardInvocation通过NSInvocation来实现消息的转发

无论在forwardInvocation方法中是否处理invocation事务,程序都不会崩溃

方法和消息的流程就到这里了,在上面的过程中你有没有注意到动态方法决议进行了两遍这个问题?它为什么会执行两遍呢?

其实第二次动态方法决议是在methodSignatureForSelectorforwardInvocation方法之间,是开始进行慢速消息转发之前再给的一次机会

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89068.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89068.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何通过 WebSocket 接口订阅实时外汇行情数据(PHP 示例)

步骤 1&#xff1a;准备工作确保已安装 PHP 和 Composer安装 WebSocket 客户端库&#xff1a;composer require textalk/websocket步骤 2&#xff1a;编写代码订阅行情以下是最简可运行的 PHP 示例&#xff0c;订阅 EUR/USD 的 1分钟K线数据&#xff1a;<?phprequire vendo…

第十八篇 数据清洗:Python智能筛选与统计:从海量Excel数据中秒级挖掘,辅助决策!你的数据分析利器!

Excel 数据挖掘Excel筛选复杂&#xff0c;统计耗时&#xff0c;无法快速挖掘数据价值1.数据筛选核心&#xff1a;df.loc与df.iloc&#xff0c;精准定位你想要的数据1.1基于条件筛选&#xff1a;过滤数据中的不恰当因素1.2 多条件组合筛选&#xff1a;精确锁定目标数据1.3字符串…

小木的机器学习日记——KNN

核心知识点总结与星级排序我为你梳理了这节课的精髓&#xff0c;并按照重要性进行了星级评定&#xff08;★★★★★为最高&#xff09;。★★★★★ 核心思想&#xff1a;回归 (Regression) 到底是什么&#xff1f;是否关键&#xff1a;是必须了解&#xff1a;是必须记住&…

Product Hunt 每日热榜 | 2025-07-15

1. OpenArt One-Click Video Story 标语&#xff1a;一键即可将任何内容转换为可随时发布的视频。 介绍&#xff1a;有一个创意、剧本、节奏&#xff0c;或者喜欢的角色吗&#xff1f;OpenArt可以将它们变成一个视觉故事—完整的画面、音乐和叙事结构&#xff0c;轻松实现&am…

Dubbo高阶难题:异步转同步调用链上全局透传参数的丢失问题

​问题场景​&#xff1a; 在分布式电商系统中&#xff0c;下单服务通过Dubbo调用库存服务&#xff08;异步接口返回CompletableFuture&#xff09;&#xff0c;同时在Gateway层通过RpcContext设置traceId。你发现&#xff1a;当库存服务内部同步调用其他服务时&#xff0c;tra…

实测两款效率工具:驾考刷题和证件照处理的免费方案

今天阿灿给大家推荐两款实用的软件&#xff0c;一款是驾考助手&#xff0c;另一款是证件照制作软件。第一款&#xff1a;驾考助手以前考驾照&#xff0c;很多人担心过不了关&#xff0c;还会花冤枉钱买VIP练习&#xff0c;精选500题。其实&#xff0c;只要用对工具&#xff0c;…

Python 函数的维护性与复用性

目录 一、从“能跑就行”到“能改不怕”——维护性的第一要义 二、单一职责与最小惊讶——维护性的纵深防御 三、可组合的乐高——复用性的第一阶梯 四、面向协议设计——复用性的第二阶梯 五、异常策略与日志——维护性的隐形护盾 七、测试金字塔——维护性的最后护城河…

C++中的模板参数 vs 函数参数:编译期与运行期的分界线

引言 在日常开发中&#xff0c;我们经常接触 函数参数&#xff0c;这是控制函数行为的最直接方式。但在 C 中还有一种强大的机制 —— 模板参数&#xff08;Template Parameters&#xff09;&#xff0c;它赋予了我们在编译期就生成代码结构的能力。 本文将通过直观的类比&…

Elasticsearch 9.x 搜索执行过程(源码解析)

1. Elasticsearch 9.x 搜索执行过程 - 源码解析 #mermaid-svg-Vp6WKKBLo3omajeq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Vp6WKKBLo3omajeq .error-icon{fill:#552222;}#mermaid-svg-Vp6WKKBLo3omajeq .error…

简单易懂,操作系统的内存管理机制是如何实现的

系统地梳理一下操作系统在“内存管理”这个重要领域中&#xff0c;到底扮演了什么角色&#xff0c;需要完成哪些核心任务。想象一下&#xff0c;操作系统是一位经验丰富的高级公寓管理员。内存&#xff1a;就是这栋高级公寓大楼。进程&#xff1a;一个个想要入住的租户。内存管…

《大数据技术原理与应用》实验报告一 熟悉常用的Linux操作和Hadoop操作

目 录 一、实验目的 二、实验平台 三、 实验内容和要求 1. 安装虚拟机 2. 熟悉常用的 Linux 命令 3. 进行 Hadoop 伪分布式安装 4. 熟悉常用的 Hadoop 操作 四、实验环境 五、实验内容与完成情况 1. 安装虚拟机 2. 熟悉常用的 Linux 命令 3. 进行 Hadoop 伪分布式…

I/O 多路复用详解笔记

I/O 多路复用笔记 什么是I/O多路复用 I/O多路复用&#xff08;I/O Multiplexing&#xff09;是一种**允许单个线程&#xff08;或进程&#xff09;监听多个I/O描述符&#xff08;fd&#xff09;**上是否就绪&#xff08;可读/可写/异常&#xff09;的方法。这种方式可以有效地管…

李白周游记50篇

https://mp.weixin.qq.com/s/7MThy1kCOATS-8ZWc09_1g 李白周游记50篇 卡西莫多 2025年07月15日 安徽 李白周游记50篇记录&#xff0c;现在写了50个小朋友&#xff0c;觉得有趣愿意加进这个连载的欢迎告知大名和出生年月&#xff0c;限20岁以下6岁以上的小朋友&#xff0c;慢…

文心一言开源版部署及多维度测评实例

文章目录第一章 文心一言开源模型简介第二章 模型性能深度实测2.1 通用能力基准测试2.1.1 文本生成质量2.1.2 数学推理能力2.2 极端场景压力测试2.2.1 高并发性能2.2.2 长上下文记忆第三章 中文特色能力解析3.1.2 文化特定理解3.2 行业术语处理3.2.1 法律文书解析3.2.2 医疗报告…

ARM单片机OTA解析(二)

文章目录二、Bootloader加载启动App代码讲解二、Bootloader加载启动App代码讲解 代码详细解析&#xff1a; typedef void (*pFunction)(void);static void DrvInit(void) {RS485DrvInit();DelayInit();SystickInit(); }#define RAM_START_ADDRESS 0x20000000 #define RAM_S…

深度解读virtio:Linux IO虚拟化核心机制

当你在虚拟机中流畅传输文件时&#xff0c;是否想过背后是谁在高效调度 IO 资源&#xff1f;当云计算平台承载千万级并发请求时&#xff0c;又是谁在底层保障数据通路的稳定&#xff1f;答案藏在一个低调却关键的技术里 ——virtio。作为 Linux IO 虚拟化的 “隐形引擎”&#…

大宗现货电子盘交易系统核心功能代码解析

系统架构设计交易系统采用分布式微服务架构&#xff0c;核心模块包括订单匹配引擎、风控系统、清算结算模块、行情推送服务和用户管理接口。系统设计遵循高并发、低延迟原则&#xff0c;使用事件驱动模型处理交易流程。订单匹配引擎实现订单簿数据结构采用红黑树或跳表实现&…

AAAI-2025 | 同济大学面向嘈杂环境的音频视觉导航!BeDAViN:大规模音频-视觉数据集与多声源架构研究

作者&#xff1a;Zhanbo Shi, Lin Zhang, Linfei Li, Ying Shen单位&#xff1a;同济大学计算机学院论文标题&#xff1a;Towards Audio-visual Navigation in Noisy Environments: A Large-scale Benchmark Dataset and An Architecture Considering Multiple Sound-Sources论…

【推荐】前端低端机和弱网环境下性能优化

下面从设计、技术选型到具体实现&#xff0c;为你详细阐述前端低端机和弱网环境下的性能优化方案。一、设计阶段 1. 降级策略分级 根据设备性能和网络质量将设备分为3个等级&#xff1a; 高性能设备&#xff1a;内存≥4GB、CPU核心数≥4、网络RTT≤200ms中等性能设备&#xff1…

HP LoadRunner 12.02 语言包安装教程(含下载/汉化步骤)

想给HP LoadRunner 12.02安装语言包&#xff08;比如中文汉化&#xff09;&#xff1f;按照这个教程一步步操作就行&#xff0c;包含下载、安装和切换语言的详细步骤&#xff0c;轻松搞定多语言支持&#xff01;适合需要本地化使用的测试人员。 先找到安装文件 安装包下载&am…