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

在这段代码中,函数 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尾调用优化机制

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# OpenCV机器视觉:车道检测

年关将至&#xff0c;春运的大幕轰轰烈烈地拉开&#xff0c;全国的公路就像一条条汹涌澎湃的 “车河”&#xff0c;各类车辆密密麻麻、川流不息&#xff0c;都朝着家的方向奔腾而去。阿强也裹挟在这归家的大军之中&#xff0c;开着他那辆被塞得满满当当、连后视镜视野都窄了几分…

在win11系统笔记本中使用Ollama部署deepseek制作一个本地AI小助手!原来如此简单!!!

大家新年好啊&#xff0c;明天就是蛇年啦&#xff0c;蛇年快乐&#xff01; 最近DeepSeek真的太火了&#xff0c;我也跟随B站&#xff0c;使用Ollama在一台Win11系统的笔记本电脑部署了DeepSeek。由于我的云服务器性能很差&#xff0c;虽然笔记本的性能也一般&#xff0c;但是…

省级数字经济发展水平数据(2011-2022年)-社科数据

省级数字经济发展水平数据&#xff08;2011-2022年&#xff09;-社科数据https://download.csdn.net/download/paofuluolijiang/90028602 https://download.csdn.net/download/paofuluolijiang/90028602 数字经济是指以数据资源为关键要素、以现代信息网络为主要载体、以信息…

Excel分区间统计分析(等步长、不等步长、多维度)

在数据分析过程中&#xff0c;可能会需要统计不同数据区间的人数、某个数据区间的平均值或者进行分组区间统计&#xff0c;本文从excel函数到数据透视表的方法&#xff0c;从简单需求到复杂需求&#xff0c;采用不同的方法进行讲解&#xff0c;尤其是通过数据透视表的强大功能大…

线程局部存储tls的原理和使用

一、背景 tls即Thread Local Storage&#xff0c;也就是线程局部存储&#xff0c;可在进程内&#xff0c;多线程按照各个线程分开进行存储。对于一些与线程上下文相关的变量&#xff0c;可放到tls中&#xff0c;减少多线程之间的数据同步的开销。 有人可能会问&#xff0c;我…

【R语言】数学运算

一、基础运算 R语言中能实现加、减、乘、除、求模、取整、取绝对值、指数、对数等运算。 x <- 2 y <- 10 # 求模 y %% x # 整除 y %/% x # 取绝对值 abs(-x) # 指数运算 y ^x y^1/x #对数运算 log(x) #log()函数默认情况下以 e 为底 双等号“”的作用等同于identical(…

2024年度总结——理想的风,吹进现实

2024年悄然过去&#xff0c;留下了太多美好的回忆&#xff0c;不得不感慨一声时间过得真快啊&#xff01;旧年风雪尽&#xff0c;新岁星河明。写下这篇博客&#xff0c;记录我独一无二的2024年。这一年&#xff0c;理想的风终于吹进现实&#xff01; 如果用一句话总结这一年&am…

基于RIP的MGRE VPN综合实验

实验拓扑 实验需求 1、R5为ISP&#xff0c;只能进行IP地址配置&#xff0c;其所有地址均配为公有IP地址&#xff1b; 2、R1和R5间使用PPP的PAP认证&#xff0c;R5为主认证方&#xff1b; R2与R5之间使用ppp的CHAP认证&#xff0c;R5为主认证方&#xff1b; R3与R5之间使用HDLC封…

ROS应用之SwarmSim在ROS 中的协同路径规划

SwarmSim 在 ROS 中的协同路径规划 前言 在多机器人系统&#xff08;Multi-Robot Systems, MRS&#xff09;中&#xff0c;SwarmSim 是一个常用的模拟工具&#xff0c;可以对多机器人进行仿真以实现复杂任务的协同。除了任务分配逻辑以外&#xff0c;SwarmSim 在协同路径规划方…

Jenkins上生成的allure report打不开怎么处理

目录 问题背景&#xff1a; 原因&#xff1a; 解决方案&#xff1a; Jenkins上修改配置 通过Groovy脚本在Script Console中设置和修改系统属性 步骤 验证是否清空成功 进一步的定制 也可以使用Nginx去解决 使用逆向代理服务器Nginx&#xff1a; 通过合理调整CSP配置&a…

unity制作动画的技巧相关注意点

1.如果不想人物执行这个动作过程中被打断&#xff0c;必须执行完这一次&#xff0c;比如人物死亡&#xff0c;就取消勾选 2.如果在制作攻击动画&#xff0c;当该动画权重默认是最大&#xff0c;我们可以给他加一个null动作&#xff0c;防止和其他动作有冲突 3.可以创建子状态机…

如何根据壁纸主题选择合适的主色调?

选择合适的主色调是壁纸设计中的关键步骤&#xff0c;它直接影响到壁纸的整体风格和情感传达。以下是一些根据壁纸主题选择主色调的技巧和建议&#xff1a; 一、明确壁纸主题 浪漫风格&#xff1a; 主题&#xff1a;营造温馨、梦幻的氛围。 主色调&#xff1a;粉色、紫色、浅蓝…

Python 数据分析 - Matplotlib 绘图

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

C语言练习(29)

13个人围成一圈&#xff0c;从第1个人开始顺序报号1、2、3。凡报到“3”者退出圈子&#xff0c;找出最后留在圈子中的人原来的序号。本题要求用链表实现。 #include <stdio.h> #include <stdlib.h>// 定义链表节点结构体 typedef struct Node {int num;struct Nod…

this、self、window、top 在 JavaScript 中的区别深入研究

在 JavaScript 开发中&#xff0c;this、self、window、top 是四个常用的概念&#xff0c;它们在不同的上下文中有着不同的用途和含义。理解它们的区别对于编写健壮的 JavaScript 代码至关重要。本文将详细解释这四个概念的区别&#xff0c;并通过代码示例进行验证。 一、this …

C++ | 红黑树

前言 本篇博客讲解c中数据结构红黑树&#xff0c;看这篇博客之前请先去看&#xff1a; C | AVL树_c avl树能有重复节点吗-CSDN博客 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青…

Linux 学习笔记__Day3

十八、设置虚拟机的静态IP 1、VMware的三种网络模式 安装VMware Workstation Pro之后&#xff0c;会在Windows系统中虚拟出两个虚拟网卡&#xff0c;如下&#xff1a; VMware提供了三种网络模式&#xff0c;分别是&#xff1a;桥接模式&#xff08;Bridged&#xff09;、NAT…

运算放大器应用电路设计笔记(六)

6.1输出失调电压发生的原因与计算 6.1.1用噪声增益进行评价 若运算放大器两个输入端接地&#xff0c;则理想运放输出为零&#xff0c;但实际的运放输出不为零&#xff0c;有一定的直流输出电压。这种直流电压称为输出失调电压。发生的原因是&#xff0c;运算放大器内部元件尤…

基于springboot+vue的流浪动物救助系统的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

小阿卡纳牌

小阿卡纳牌 风&#xff1a;热湿 火&#xff1a;热干 水&#xff1a;冷湿 土&#xff1a;冷干 火风&#xff1a;温度相同&#xff0c;但是湿度不同&#xff0c;二人可能会在短期内十分热情&#xff0c;但是等待热情消退之后&#xff0c;会趋于平淡。 湿度相同、温度不同&#x…