Effective Objective-C 2.0 读书笔记—— objc_msgSend

news2025/1/30 12:17:12

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();
    }
}

在这段代码中,函数 printHelloprintGoodbye 的调用是直接的,编译器在编译时就能确定这些函数的调用路径。在这个过程中,函数名直接指向特定的地址,编译器无需做任何动态决策。所有的函数调用都在编译时已经解析好了,这就是静态绑定

再来看下一个例子,如果我们将代码改写为以下的内容

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_msgSendobjc_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_stretobjc_msgSend_fpret 等效的函数,用于处理发给超类的相应消息。

尾调用优化

尾调用优化(TCO)是一种编译器优化技术。它的核心思想是 如果一个函数的最后一个操作是调用另一个函数,并且这个函数的返回值不会被进一步使用,那么编译器可以避免为这个函数创建新的栈帧

尾调用的条件是:

  1. 最后一个操作是直接调用另一个函数。
  2. 返回值没有进一步的操作(比如乘法、加法等)。

具体是否使用尾调用优化的情景,可以看这篇文章

正常我们调用一个递归的函数,CPU会不断向调用堆栈之中推入栈帧,如下图

image-20250126102356184

每次我们通过 objc_msgSend 调用一个方法时,都会为这次调用创建一个新的栈帧。栈帧包含了当前方法调用的信息,如参数、返回地址等。由于 OC 的动态特性,objc_msgSend 需要在调用之前处理很多工作,比如查找方法的实现、解析参数等,这些都需要在栈中存储信息。

书中的原话:

只有当某个函数的最后一个操作仅仅是调用另一个函数,并且不使用该函数的返回值时,才可以执行“尾调用优化”(Tail Call Optimization)。
在 Objective-C 中,objc_msgSend 的尾调用优化非常关键。如果没有进行尾调用优化,每次调用 Objective-C 方法时,都需要为调用 objc_msgSend 函数准备一个新的“栈帧”。这些栈帧会在 “栈踪迹”(stack trace)中可见。

如果不进行尾调用优化,调用栈会不断增长,可能会导致“栈溢出”(stack overflow)现象。栈溢出通常发生在递归调用或大量函数调用没有得到优化时,导致栈空间耗尽。

以下是使用尾调用优化时,函数栈帧的变化,都是共用同一个栈帧

image-20250126102323756

总结

消息其实就是由接受者,选择子和方法参数所构成,给对象发送消息其实就是相当于在该对象之中调用方法。通过 objc_msgSend,方法的调用变得非常灵活,可以在运行时根据对象和方法选择器找到并执行相应的方法。这个机制为我们后面学习的动态方法解析、消息转发、方法交换等强大的特性做了铺垫。

参考文章

iOS objc_msgSend尾调用优化机制

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

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

相关文章

使用EVE-NG-锐捷实现OSPF

一、OSPF基础知识 Open shortest Path First(OSPF)开放式最短路径优先协议 1.OSPF的关系状态 (1)邻居关系(TWO-WAY) 只发送hello包不发送LSA包(链路状态通告包) (2)邻接关系(FULL) OSPF设备与设备之间相互建立OSPF关系&#xff0c;初始为邻居关系(TWO-WAY)状态&#xff0…

Baklib赋能下的内容中台智能化推荐系统解析与展望

内容概要 在数字化时代&#xff0c;内容中台的智能化推荐系统正逐渐成为各类企业提升用户体验与运营效率的重要工具。该系统通过集成和分析大量用户数据及内容信息&#xff0c;能够实现精准的个性化推荐&#xff0c;为用户提供最相关的内容。 以下是内容中台智能化推荐系统的…

2024年记 | 凛冬将至

放弃幻想&#xff0c;准备斗争&#xff01; 考研or就业&#xff1f; 上大学以来&#xff0c;考研上名校在我的心里一直是一颗种子&#xff0c;2024年初&#xff0c;当时的想法是考研和就业两手抓。买了张宇的高数现代&#xff0c;想要死磕&#xff01; 也记了挺多笔记... 如果…

数据分析系列--②RapidMiner导入数据和存储过程

一、下载数据 点击下载AssociationAnalysisData.xlsx数据集 二、导入数据 1. 在本地计算机中创建3个文件夹 2. 从本地选择.csv或.xlsx 三、界面说明 四、存储过程 将刚刚新建的过程存储到本地 Congratulations, you are done.

进程通讯——类型和发展

进程常用交互方法如上

深度学习:从基础到前沿

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a;【Linux】进程地址空间与虚拟地址空间 &#x1f516;流水不争&#xff0c;争的是滔滔不 一、深度学习的基础知…

【项目】基于Qt开发的音乐播放软件

目录 项目介绍 项目概述 界面开发 界面分析 创建工程 主界面布局设计 窗口主框架设计 界面美化 主窗口设定 添加图片资源 head处理 播放控制区处理 自定义控件 BtForm 推荐页面 自定义CommonPage 自定义ListItemBox 自定义MusicSlider 自定义VolumeTool 音…

C语言,无法正常释放char*的空间

问题描述 #include <stdio.h> #include <stdio.h>const int STRSIZR 10;int main() {char *str (char *)malloc(STRSIZR*sizeof(char));str "string";printf("%s\n", str);free(str); } 乍一看&#xff0c;这块代码没有什么问题。直接书写…

数据可视化的图表

1.折线图反映了一段时间内事物连续的动态变化规律,适用于描述一个变量随另一个变量变化的趋势,通常用于绘制连续数据,适合数据点较多的情况。 2.散点图是以直角坐标系中各点的密集程度和变化趋势来表示两种现象间的相关关系&#xff0c;常用于显示和比较数值。当要在不考虑时间…

简易CPU设计入门:控制总线的剩余信号(四)

项目代码下载 请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 CSDN文章&#xff1a;下载本项目代码 上述链接为本项目…

基础IO(2)

基础IO&#xff08;2&#xff09; 理解“⼀切皆⽂件” ⾸先&#xff0c;在windows中是⽂件的东西&#xff0c;它们在linux中也是⽂件&#xff1b;其次⼀些在windows中不是⽂件的东西&#xff0c;⽐如进程、磁盘、显⽰器、键盘这样硬件设备也被抽象成了⽂件&#xff0c;你可以使…

IDM-VTON本地部署教程:双重编码 + 文字提示,解锁真实野外试穿

一、介绍 IDM-VTON&#xff1a;改进扩散模型&#xff0c;实现真实的野外虚拟试穿。 技术原理&#xff1a;改进扩散模型&#xff0c;利用视觉编码器提取服装高级语义信息并与交叉注意力层融合&#xff0c;通过并行 UNet 结构的 GarmentNet 捕捉服装低级特征并与自注意力层结合&…

【2024年华为OD机试】 (C卷,200分)- 矩阵匹配(JavaScriptJava PythonC/C++)

一、问题描述 问题描述 给定一个大小为 ( N \times M )&#xff08;( N \leq M )&#xff09;的矩阵&#xff0c;从中选出 ( N ) 个数&#xff0c;要求任意两个数字不能在同一行或同一列。求选出来的 ( N ) 个数中第 ( K ) 大的数字的最小值。 输入描述 输入矩阵要求&#…

AI 浪潮席卷中国年,开启科技新春新纪元

在这博主提前祝大家蛇年快乐呀&#xff01;&#xff01;&#xff01; 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间&#xff0c;AI 技术也展现出了巨大的潜力&#xff0c;为中国年带…

Python 数据分析 - Matplotlib 绘图

Python 数据分析 - Matplotlib 绘图 简介绘图折线图单线多线子图 散点图直方图条形图纵置横置多条 饼图 简介 Matplotlib 是 Python 提供的一个绘图库&#xff0c;通过该库我们可以很容易的绘制出折线图、直方图、散点图、饼图等丰富的统计图&#xff0c;安装使用 pip install…

搭建Spark分布式集群

1&#xff0c;下载 下载 spark-3.5.4-bin-without-hadoop.tgz 地址&#xff1a; https://downloads.apache.org/spark/spark-3.5.4/ 2&#xff0c;安装 通过虚拟机设置共享文件夹将需要的安装包复制到linux虚拟机中 localhost1。虚拟机的共享盘在 /mnt/hgfs/。 将共享盘安装…

新年祝词(原创)

新年将至&#xff0c;福进万户。 家家团圆&#xff0c;事事顺心。 喜迎财神&#xff0c;多寿添金。 瑞兽迎春&#xff0c;炮竹声起。 趋吉避凶&#xff0c;蛇年大吉。 中华崛起&#xff0c;人人自强。 天下大同&#xff0c;百姓富足。 有情有义&#xff0c;平易近人。 …

线上突发:MySQL 自增 ID 用完,怎么办?

线上突发&#xff1a;MySQL 自增 ID 用完&#xff0c;怎么办&#xff1f; 1. 问题背景2. 场景复现3. 自增id用完怎么办&#xff1f;4. 总结 1. 问题背景 最近&#xff0c;我们在数据库巡检的时候发现了一个问题&#xff1a;线上的地址表自增主键用的是int类型。随着业务越做越…

ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据

简介 在这个系列的上一篇文章中&#xff0c;我们介绍了ESP32 I2S音频总线的相关知识&#xff0c;简要了解了什么是I2S总线、它的通信格式&#xff0c;以及相关的底层API函数。没有看过上篇文章的可以点击文章进行回顾&#xff1a; ESP32 I2S音频总线学习笔记&#xff08;一&a…

一文简单回顾Java中的String、StringBuilder、StringBuffer

简单说下String、StringBuilder、StringBuffer的区别 String、StringBuffer、StringBuilder在Java中都是用于处理字符串的&#xff0c;它们之间的区别是String是不可变的&#xff0c;平常开发用的最多&#xff0c;当遇到大量字符串连接的时候&#xff0c;就用StringBuilder&am…