Effective Objective-C 2.0 读书笔记—— objc_msgSend
Effective Objective-C 2.0 读书笔记—— objc_msgSend
文章目录
- Effective Objective-C 2.0 读书笔记—— objc_msgSend
- 引入——静态绑定和动态绑定
- OC之中动态绑定的实现
- 方法签名
- 方法列表
- 其他方法
- `objc_msgSend_stret`
- `objc_msgSend_fpret`
- `objc_msgSendSuper`
- 尾调用优化
- 总结
- 参考文章
引入——静态绑定和动态绑定
我们知道OC实际上是在C的基础上引入面向对象的内容,我们先来理解在C语言之中函数调用的方式——静态绑定(static binding)也就是编译器在编译的时候就能够知道运行时所调用的函数,以书中的代码为例:
#include <stdio.h>void printHello() {printf("Hello, world!\n");
}void printGoodbye() {printf("Goodbye, world!\n");
}void doTheThing(int type) {if (type == 0) {printHello();} else {printGoodbye();}
}
在这段代码中,函数 printHello 和 printGoodbye 的调用是直接的,编译器在编译时就能确定这些函数的调用路径。在这个过程中,函数名直接指向特定的地址,编译器无需做任何动态决策。所有的函数调用都在编译时已经解析好了,这就是静态绑定。
再来看下一个例子,如果我们将代码改写为以下的内容
void printHello() {printf("Hello, world!\n");
}void printGoodbye() {printf("Goodbye, world!\n");
}void doTheThing(int type) {void (*fnc)(); // 定义一个函数指针if (type == 0) {fnc = printHello; // 如果type为0,指向printHello函数} else {fnc = printGoodbye; // 否则,指向printGoodbye函数}fnc(); // 调用通过指针指定的函数
}
这种方法就是动态绑定(dynamic binding)。在编译时,编译器并不知道 fnc 最终会指向哪个函数,它只能知道 fnc 是一个指向 void() 类型的函数指针,但不能确定它指向的函数直到程序运行时。也就是说,函数的调用和方法的选择是在程序运行时才决定的,因为编译器无法在编译时确定到底调用哪个函数。
OC之中动态绑定的实现
在Objective- C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得Objective- C成为 一门真正的动态语言。
我们模拟给对象发送通知:
id returnValue = [someObject messageName:parameter];
编译器看到这条命令之后,就会自动的将其转化一条标准的C语言函数调用objc_msgSend ,objc_msgSend 是 Objective-C 的一个底层函数,它是 Objective-C 动态消息传递机制的核心。通过它,消息被发送给对象,进而调用对象的某个方法。
方法签名
objc_msgSend 的函数签名如下(简化版):
id objc_msgSend(id self, SEL _cmd, ...);
self:消息的接收者,即对象。_cmd:选择子- 后面的
...:是方法参数,消息的实际内容。
所以刚刚上面的代码,就可以转化为以下函数
id returnValue = objc_msgSend (someObject, @selector (messageName:), parameter) ;
接下来就是objc_msgSend根据接受者类型以及选择子来调用对应的方法,借用书中的原话:
方法需要在接收者所属的类中搜寻其“方法列表”(list of methods)。如果能找到与选择子名称相符的方法,就跳至其实现代码。若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行 “消息转发”(message forwarding)操作。
方法列表
这里说到了方法列表,就顺带讲一下:
在 Objective-C 中,每个类都有一套方法列表,用于存储该类的所有实例方法、类方法及它们的相关信息。这些方法列表(Method List)用数组的形式存储了与类相关的所有方法,并且可以通过运行时(Runtime)机制进行动态查找和调用。
我们知道方法有两种,类方法和实例方法,那么方法列表也可以分成两种:
实例方法(Instance Methods):这些方法是类的实例(对象)调用的。
类方法(Class Methods):这些方法是类本身(而非类的实例)调用的。
获取实例方法列表
使用 class_copyMethodList 函数可以获取某个类的实例方法列表。返回的是一个 Method 数组,数组中包含了该类的所有实例方法。
unsigned int methodCount = 0;
Method *methods = class_copyMethodList([MyClass class], &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {Method method = methods[i];SEL methodSelector = method_getName(method); // 获取方法的选择子const char *methodTypeEncoding = method_getTypeEncoding(method); // 获取方法类型编码NSLog(@"Method name: %s", sel_getName(methodSelector));
}
free(methods);
class_copyMethodList:返回类的实例方法列表。method_getName:获取方法的选择子。method_getTypeEncoding:获取方法的类型编码。sel_getName:将选择器转换为字符串。
获取类方法列表
获取类方法列表的过程和获取实例方法列表类似,只不过你需要使用 class_copyMethodList 获取的是类本身(而不是类的实例)的方法列表。
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(object_getClass([MyClass class]), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {Method method = methods[i];SEL methodSelector = method_getName(method);const char *methodTypeEncoding = method_getTypeEncoding(method);NSLog(@"Class method name: %s", sel_getName(methodSelector));
}
free(methods);
object_getClass:获取类的元类(meta-class),元类包含了类方法。
其他方法
在使用objc_msgSend的时候,我们注意到我们的返回值是OC对象,但如果我们这个函数返回的是其他内容,例如:结构体,浮点数和超类的极端情况出现时,objc_msgSend可能就有局限性了。那么自然有其他的方法来解决这些问题
objc_msgSend_stret
当待发送的消息返回结构体时,可交由此函数处理。
前提条件:只有当 CPU 的寄存器能够容纳返回类型时,此函数才能处理该消息。
如果返回的结构体太大,无法完全容纳于 CPU 寄存器中,那么将会由另一个函数执行消息派发。此时,那个函数会在栈上分配一个变量来处理返回的结构体。
objc_msgSend_fpret
当消息返回的是浮点数时,可交由此函数处理。
原因:在某些 CPU 架构中,调用函数时需要特别处理浮点数寄存器(Floating-point register),即浮点数的处理方式与普通寄存器不同。因此,通常的 objc_msgSend 在这种情况下并不合适。此函数主要用于处理像 x86 架构等需要特殊处理的 CPU 环境。
objc_msgSendSuper
如果要给超类发送消息(例如 [super message:parameter]),则交由此函数处理。
此外,还有两个与 objc_msgSend_stret 和 objc_msgSend_fpret 等效的函数,用于处理发给超类的相应消息。
尾调用优化
尾调用优化(TCO)是一种编译器优化技术。它的核心思想是 如果一个函数的最后一个操作是调用另一个函数,并且这个函数的返回值不会被进一步使用,那么编译器可以避免为这个函数创建新的栈帧。
尾调用的条件是:
- 最后一个操作是直接调用另一个函数。
- 返回值没有进一步的操作(比如乘法、加法等)。
具体是否使用尾调用优化的情景,可以看这篇文章
正常我们调用一个递归的函数,CPU会不断向调用堆栈之中推入栈帧,如下图

每次我们通过 objc_msgSend 调用一个方法时,都会为这次调用创建一个新的栈帧。栈帧包含了当前方法调用的信息,如参数、返回地址等。由于 OC 的动态特性,objc_msgSend 需要在调用之前处理很多工作,比如查找方法的实现、解析参数等,这些都需要在栈中存储信息。
书中的原话:
只有当某个函数的最后一个操作仅仅是调用另一个函数,并且不使用该函数的返回值时,才可以执行“尾调用优化”(Tail Call Optimization)。
在 Objective-C 中,objc_msgSend的尾调用优化非常关键。如果没有进行尾调用优化,每次调用 Objective-C 方法时,都需要为调用objc_msgSend函数准备一个新的“栈帧”。这些栈帧会在 “栈踪迹”(stack trace)中可见。如果不进行尾调用优化,调用栈会不断增长,可能会导致“栈溢出”(stack overflow)现象。栈溢出通常发生在递归调用或大量函数调用没有得到优化时,导致栈空间耗尽。
以下是使用尾调用优化时,函数栈帧的变化,都是共用同一个栈帧

总结
消息其实就是由接受者,选择子和方法参数所构成,给对象发送消息其实就是相当于在该对象之中调用方法。通过 objc_msgSend,方法的调用变得非常灵活,可以在运行时根据对象和方法选择器找到并执行相应的方法。这个机制为我们后面学习的动态方法解析、消息转发、方法交换等强大的特性做了铺垫。
参考文章
iOS objc_msgSend尾调用优化机制
相关文章:
Effective Objective-C 2.0 读书笔记—— objc_msgSend
Effective Objective-C 2.0 读书笔记—— objc_msgSend 文章目录 Effective Objective-C 2.0 读书笔记—— objc_msgSend引入——静态绑定和动态绑定OC之中动态绑定的实现方法签名方法列表 其他方法objc_msgSend_stretobjc_msgSend_fpretobjc_msgSendSuper 尾调用优化总结参考文…...
[MySQL]事务的隔离级别原理与底层实现
目录 1.为什么要有隔离性 2.事务的隔离级别 读未提交 读提交 可重复读 串行化 3.演示事务隔离级别的操作 查看与设置事务的隔离级别 演示读提交操作 演示可重复读操作 1.为什么要有隔离性 在真正的业务场景下,MySQL服务在同一时间一定会有大量的客户端进程…...
项目升级Sass版本或升级Element Plus版本遇到的问题
项目升级Sass版本或升级Element Plus版本遇到的问题 如果项目有需求需要用到高版本的Element Plus组件,则需要升级相对应的sass版本,Element 文档中有提示,2.8.5及以后得版本,sass最低支持的版本为1.79.0,所升级sass、…...
C++中,存储两个相同类型的数据,数据结构
在C中,存储两个相同类型的数据,可以使用多种数据结构。这里有几种常见且合适的选择: 简单的变量: 最直接的方式就是使用两个独立的变量。这种方法简单直观,但不够结构化。 cpp int a 5; int b 10; std::pair&#x…...
python实战(十五)——中文手写体数字图像CNN分类
一、任务背景 本次python实战,我们使用来自Kaggle的数据集《Chinese MNIST》进行CNN分类建模,不同于经典的MNIST数据集,我们这次使用的数据集是汉字手写体数字。除了常规的汉字“零”到“九”之外还多了“十”、“百”、“千”、“万”、“亿…...
[论文阅读] (37)CCS21 DeepAID:基于深度学习的异常检测(解释)
祝大家新春快乐,蛇年吉祥! 《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正࿰…...
Linux - 进程间通信(2)
目录 2、进程池 1)理解进程池 2)进程池的实现 整体框架: a. 加载任务 b. 先描述,再组织 I. 先描述 II. 再组织 c. 创建信道和子进程 d. 通过channel控制子进程 e. 回收管道和子进程 问题1: 解答1ÿ…...
Kafka 消费端反复 Rebalance: `Attempt to heartbeat failed since group is rebalancing`
文章目录 Kafka 消费端反复 Rebalance: Attempt to heartbeat failed since group is rebalancing1. Rebalance 过程概述2. 错误原因分析2.1 消费者组频繁加入或退出2.1.1 消费者故障导致频繁重启2.1.2. 消费者加入和退出导致的 Rebalance2.1.3 消费者心跳超时导致的 Rebalance…...
SpringBoot+Electron教务管理系统 附带详细运行指导视频
文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.查询课程表代码2.保存学生信息代码3.用户登录代码 一、项目演示 项目演示地址: 视频地址 二、项目介绍 项目描述:这是一个基于SpringBootElectron框架开发的教务管理系统。首先ÿ…...
操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之控制台工作
前言 在 Linux 内核中,字符设备主要包括控制终端设备和串行终端设备,对这些设备的输入输出涉及控制台驱动程序,这包括键盘中断驱动程序 keyboard.S 和控制台显示驱动程序 console.c,还有终端驱动程序与上层程序之间的接口部分。 终端驱动程序…...
Autogen_core: Message and Communication
目录 完整代码代码解释1. 消息的数据类:2. 创建代理人(MyAgent):3. 创建和运行代理人的运行时环境:4. 根据发送者路由消息的代理(RoutedBySenderAgent):5. 创建和运行带路由的代理&a…...
ComfyUI工作流教程、软件使用、开发指导、模型下载
在人工智能和设计技术迅速发展的今天,AI赋能的工作流已成为创意设计与生产的重要工具。无论是图片处理、服装试穿,还是室内设计与3D建模,这些智能化的解决方案极大地提高了效率和创作质量。 为了帮助设计师、开发者以及AI技术爱好者更好地利用这些工具,我们整理了一份详尽…...
零基础Vue学习1——Vue学习前环境准备
目录 环境准备 创建Vue项目 项目目录说明 后续开发过程中常用命令 环境准备 安装开发工具:vscode、webstorm、idea都可以安装node:V22以上版本即可安装pnpm 不知道怎么安装的可以私信我教你方法 创建Vue项目 本地新建一个文件夹,之后在文件夹下打开…...
定西市建筑房屋轮廓数据shp格式gis无偏移坐标(字段有高度和楼层)内容测评
定西市建筑房屋轮廓数据是GIS(Geographic Information System,地理信息系统)领域的重要资源,用于城市规划、土地管理、环境保护等多个方面。这份2022年的数据集采用shp(Shapefile)格式,这是一种…...
汉语向编程指南
汉语向编程指南 一、引言王阳明代数与流形学习理论慢道缓行理性人类型指标系统为己之学与意气实体过程晏殊几何学半可分离相如矩阵与生成气质邻域镶嵌气度曲面细分生成气质邻域镶嵌气度曲面细分社会科学概论琴生生物机械科技工业研究所软凝聚态物理开发工具包琴生生物机械 报告…...
Writing an Efficient Vulkan Renderer
本文出自GPU Zen 2。 Vulkan 是一个新的显式跨平台图形 API。它引入了许多新概念,即使是经验丰富的图形程序员也可能不熟悉。Vulkan 的主要目标是性能——然而,获得良好的性能需要深入了解这些概念及其高效应用方法,以及特定驱动程序实现的实…...
AI常见的算法
人工智能(AI)中常见的算法分为多个领域,如机器学习、深度学习、强化学习、自然语言处理和计算机视觉等。以下是一些常见的算法及其用途: 1. 机器学习 (Machine Learning) 监督学习 (Supervised Learning) 线性回归 (Linear Regr…...
LibreChat
文章目录 一、关于 LibreChat✨特点 二、使用LibreChat🪶多合一AI对话 一、关于 LibreChat LibreChat 是增强的ChatGPT克隆:Features Agents, Anthropic, AWS, OpenAI, Assistants API, Azure, Groq, o1, GPT-4o, Mistral, OpenRouter, Vertex AI, Gemi…...
Spring Boot 日志:项目的“行车记录仪”
一、什么是Spring Boot日志 (一)日志引入 在正式介绍日志之前,我们先来看看上篇文章中(Spring Boot 配置文件)中的验证码功能的一个代码片段: 这是一段校验用户输入的验证码是否正确的后端代码,…...
Spring Boot 实现文件上传和下载
文章目录 Spring Boot 实现文件上传和下载一、引言二、文件上传1、配置Spring Boot项目2、创建文件上传控制器3、配置文件上传大小限制 三、文件下载1、创建文件下载控制器 四、使用示例1、文件上传2、文件下载 五、总结 Spring Boot 实现文件上传和下载 一、引言 在现代Web应…...
从零构建树莓派人脸识别门禁:硬件选型、环境部署与实战避坑
1. 硬件选型与采购清单 第一次玩树莓派人脸识别项目时,我在淘宝上花了整整三天对比各种硬件参数。当时最纠结的就是摄像头模块——普通USB摄像头才30块钱,而官方推荐的Raspberry Pi Camera Module V2要200多。后来实测发现,这差价真不能省。 …...
为什么92%的FastAPI流式AI项目在高并发下崩溃?深度解析event loop争用、response.body迭代器生命周期与uvicorn worker模型冲突
第一章:FastAPI 2.0流式AI响应的高并发失效现象全景透视当FastAPI 2.0被用于承载大语言模型(LLM)的SSE(Server-Sent Events)或分块Transfer-Encoding: chunked流式响应时,大量并发请求下常出现连接提前终止…...
3分钟快速上手AdGuard浏览器扩展:开源广告拦截工具全平台安装指南
3分钟快速上手AdGuard浏览器扩展:开源广告拦截工具全平台安装指南 【免费下载链接】AdguardBrowserExtension AdGuard browser extension 项目地址: https://gitcode.com/gh_mirrors/ad/AdguardBrowserExtension AdGuard浏览器扩展是一款开源、免费的广告拦截…...
小米智能家居与Home Assistant完美融合:打造高效智能家居生态
小米智能家居与Home Assistant完美融合:打造高效智能家居生态 【免费下载链接】ha_xiaomi_home Xiaomi Home Integration for Home Assistant 项目地址: https://gitcode.com/GitHub_Trending/ha/ha_xiaomi_home 小米智能家居Home Assistant集成是由小米官方…...
AI人脸隐私卫士快速部署指南:3步启动WebUI界面,开箱即用
AI人脸隐私卫士快速部署指南:3步启动WebUI界面,开箱即用 1. 引言:你的隐私,需要一道智能防线 你有没有过这样的困扰?公司团建拍了张大合照,想发朋友圈分享喜悦,却担心照片里同事们的隐私&…...
3个关键场景与4步操作:深入解析RevokeMsgPatcher防撤回工具的技术实现与应用实践
3个关键场景与4步操作:深入解析RevokeMsgPatcher防撤回工具的技术实现与应用实践 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) 项目…...
Claude 90分钟挖穿20年漏洞!5w星“安全”系统跌下神坛,Linux内核也未能幸免
鹭羽 发自 凹非寺量子位 | 公众号 QbitAIGitHub狂揽5w星、以安全著称的Ghost CMS,刚刚跌下了神坛。只因Anthropic的研究员给Claude下达了一个指令——找出系统漏洞。结果90分钟,精准定位Ghost CMS首个高危漏洞,并在无身份验证的情况下窃取到管…...
4个关键步骤:用vscode-ai-toolkit实现智能应用开发全流程
4个关键步骤:用vscode-ai-toolkit实现智能应用开发全流程 【免费下载链接】vscode-ai-toolkit 项目地址: https://gitcode.com/GitHub_Trending/vs/vscode-ai-toolkit AI Toolkit for Visual Studio Code是一款专为简化生成式AI应用开发设计的强大VS Code扩…...
Minecraft 1.12.2 彩色渐变字体模组:打造个性化聊天与物品命名
1. RGB Chat模组:让你的Minecraft文字绚丽多彩 还在用单调的白色文字聊天吗?RGB Chat模组彻底改变了Minecraft 1.12.2版本的文字显示方式。这个轻量级模组只有几百KB大小,却能给你的游戏体验带来质的飞跃。我第一次在服务器里看到彩色渐变文字…...
如何用Tool-SQL解决Text2SQL中的条件不匹配问题?实战案例分享
实战解析:用Tool-SQL攻克Text2SQL条件不匹配难题 当数据工程师面对"帮我找出上季度华东区销售额超50万但退货率低于5%的客户"这类业务查询时,传统Text2SQL方案常陷入条件错配的泥潭——系统生成的SQL要么遗漏关键约束,要么将"…...
