Effective Objective-C 2.0 读书笔记—— 消息转发
Effective Objective-C 2.0 读书笔记—— 消息转发
文章目录
- Effective Objective-C 2.0 读书笔记—— 消息转发
- 前言
- 消息转发机制概述
- 动态方法解析
- 处理`@dynamic`的属性
- 用于懒加载
- 消息转发
- 快速消息转发
- 完整消息转发
- 总结
前言
在前面我学习了关联对象和objc_msgSend的相关内容,初步了解了OC的动态机制,在我们的objc_msgSend的执行操作之中,我们提到了,如果对象接受了无法解读的消息之后,就会进行消息转发。那么什么消息可以被理解呢?最基本的就是,我们的程序要实现对应的方法,由于OC动态语言的特性,我们在编译期的时候仍可以在类之中添加方法,所以当对象接受到无法解析的消息时就会启动消息转发机制(message forwarding)。
消息转发机制概述
消息转发一共由两种情况
- 动态方法解析(Dynamic Method Resolution):如果一个对象没有实现某个方法,Objective-C 会尝试在运行时为该方法动态添加实现。
- 消息转发(Message Forwarding):如果对象无法处理该消息且无法动态解析方法,系统会尝试将该消息转发给其他对象来处理。
动态方法解析
对于动态方法解析来说,在这个阶段之中先征询接受者,所属的类,看其是否能动态的添加方法去处理这个未知的选择子
如果是实例方法未能识别,那么首先将调用其所属类的下列类方法:
+(BOOL) resolveInstanceMethod: (SEL) selector
如果是类方法尚未被实现,则调用一下方法
+(BOOL) resolveClassMethod: (SEL) selector
这两个方法都返回的是Boolean类型,表示能否新增这个方法处理这个选择子
处理@dynamic的属性
书中用一个被@dynamic修饰的属性为例,使用这个方法为属性生成setter和getter方法
id autoDictionaryGetter(id self, SEL _cmd) {// 这里可以实现获取字典的逻辑,可能是从某个缓存或者实际数据源获取return objc_getAssociatedObject(self, _cmd);
}void autoDictionarySetter(id self, SEL _cmd, id value) {// 这里可以实现设置字典的逻辑,可能是更新缓存或者实际数据源objc_setAssociatedObject(self, _cmd, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}+ (BOOL)resolveInstanceMethod:(SEL)selector {NSString *selectorString = NSStringFromSelector(selector);// 检查是否是动态属性的 getter 或 setter 方法if ([selectorString hasPrefix:@"set"]) {// 如果是 setter 方法(即 setAutoDictionary:)class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@"); // 'v@:@' 表示返回 void 类型,self 和 _cmd 参数,最后是一个 id 类型的参数return YES;}// 如果是 getter 方法(即 autoDictionary)if ([selectorString hasPrefix:@"autoDictionary"]) {class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:"); // '@@:' 表示返回 id 类型,self 和 _cmd 参数return YES;}// 如果方法不是 setter 或 getter,则调用父类的 resolveInstanceMethod:return [super resolveInstanceMethod:selector];
}
简单解释一下代码的内容:
class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
class_addMethod是运行时函数,允许你动态地为某个类添加方法。- 它的参数依次是:
self:要为哪个类添加方法,通常是当前类。selector:方法选择器,表示要添加的方法的名字。(IMP)autoDictionarySetter:方法实现,IMP是指向方法实现的指针,这里是autoDictionarySetter函数的指针。"v@:@":方法的签名,描述了方法的参数和返回值类型——表示返回 void 类型,self 和 _cmd 参数,最后是一个 id 类型的参数
IMP是 Implementation Pointer(实现指针)的缩写,是一种在 Objective-C 中表示方法实现的指针类型。具体来说,它指向一个实际的函数实现,并允许在运行时动态地调用该函数。
IMP 的类型定义如下:
typedef id (*IMP)(id, SEL, ...);
用于懒加载
相比直接声明并实现方法,动态方法解析提供了更多的控制权,可以根据需要决定是否加载方法的具体实现。
使用场景:一个类可能定义了很多方法,但并不是所有方法都会被使用,但即使不被使用编译器也会为它分配元数据。通过动态方法解析,可以避免为未使用的方法占用内存。方法实现的绑定延迟到实际调用时完成,减少类加载和初始化的开销。
#import "JCClass.h"
#import <objc/runtime.h>
@implementation JCClass
+ (BOOL)resolveInstanceMethod:(SEL)selector {NSString *selectorString = NSStringFromSelector(selector);NSLog(@"enter");// 检查方法选择器是否为 'heavyComputation',这是我们需要懒加载的方法if ([selectorString isEqualToString:@"heavyComputation"]) {// 使用 class_addMethod 为 `heavyComputation` 方法动态添加实现class_addMethod(self, selector, (IMP)heavyComputation, "@@:");return YES; // 返回 YES 表示我们已经为该方法添加了实现}return [super resolveInstanceMethod:selector]; // 调用父类的实现
}// 重的计算过程,模拟复杂计算
id heavyComputation(id self, SEL _cmd) {NSLog(@"1");// 模拟一个复杂的计算过程NSLog(@"Performing heavy computation...");// 假设我们计算结果并缓存它NSString *result = @"This is the result of the heavy computation.";// 将计算结果存储到关联对象中,以便下次访问时直接返回objc_setAssociatedObject(self, _cmd, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);return result;
}
@endJCClass *obj = [[JCClass alloc] init];
NSLog(@"%@",[obj heavyComputation]);
NSLog(@"%@",[obj heavyComputation]);
这个程序运行得到以下结果,可以看到当我们调用heavyComputation的第一次就会进入resolveInstanceMethod,第二次就会直接调用经过动态绑定的方法

heavyComputation 是一个普通的函数,它的存在独立于任何类。通过 class_addMethod,它才被绑定为某个类的方法。
这里需要注意,我们需要在我们的MyClass类的头文件声明这个heavyComputation的方法,让编译器相信这个函数的存在
- (id)heavyComputation;
抑或者我们可以不在头文件之中声明,直接使用,就可以绕过编译器的警告:
[obj performSelector:@selector(heavyComputation)];
当我在学习到这一部分的时候,其实是很有疑问的,懒加载的目的是不让编译器在编译的过程之中进行对方法进行加载,但是C语言写成的函数还是放在了程序之中,只是没有绑定对象,这样做能节省什么资源呢?
在C语言之中函数在程序启动前就已经存在,并且占用一定的内存资源,但是它的内存分配主要体现在程序的 代码段,相比 C 语言函数,OC 方法,由于其动态的性质,内存开销通常更大,因为方法不仅包括代码,还包括方法名称 (
SEL),方法的实现地址 (IMP),方法所属的类(元类里存储方法列表),其他运行时需要的元信息。所以我们在编写OC程序的时候,在遇到不一定需要的功能时,可以避免加载,有利于提高程序的使用效率
消息转发
消息转发又被分为两个部分,一个是快速消息转发(Forwarding to another object),另一个是完整消息转发(Forwarding the Message)
快速消息转发
当我们在动态方法解析没有找到处理选择子的方法时,当前对象还有第二次机会对这个选择子的信息进行转发,我们就称为快速消息转发
快速消息转发机制通过 forwardingTargetForSelector: 方法将消息转发给另一个对象,这个对象会尝试执行该方法。如果目标对象能响应该消息,则继续处理。
- (id)forwardingTargetForSelector:(SEL)selector {if (selector == @selector(foo)) {return someOtherObject; // 将消息转发给 someOtherObject}return [super forwardingTargetForSelector:selector];
}
其中这个someOtherObject是一个实例,如果 someOtherObject 能响应 foo 方法,则该方法会在 someOtherObject 上执行。
完整消息转发
如果 forwardingTargetForSelector: 返回了 nil 或者目标对象不能处理该消息,系统会进入完整的消息转发阶段,即通过 methodSignatureForSelector: 和 forwardInvocation: 来处理。
首先,创建一个 NSInvocation 对象,将与尚未处理的消息相关的所有细节封装在其中。该对象包含以下信息:
- 选择子(Selector):即方法名称。
- 目标(Target):接收消息的对象。
- 参数:调用方法时传递给方法的参数。
当触发 NSInvocation 对象时,消息派发系统(message-dispatch system)会介入,负责将消息转发给目标对象,执行相应的方法。
然而这样实现出来的方法与“备援接收者” 方案所实现的方法等效,所以很少有人采用这么简单的实现方式。
流程图:

总结
- 若对象无法响应某个选择子,则进人消息转发流程。
- 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
- 对象可以把其无法解读的某些选择子转交给其他对象来处理。
- 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。
相关文章:
Effective Objective-C 2.0 读书笔记—— 消息转发
Effective Objective-C 2.0 读书笔记—— 消息转发 文章目录 Effective Objective-C 2.0 读书笔记—— 消息转发前言消息转发机制概述动态方法解析处理dynamic的属性用于懒加载 消息转发快速消息转发完整消息转发 总结 前言 在前面我学习了关联对象和objc_msgSend的相关内容&a…...
【Python-办公自动化】实现自动化输出json数据类型的分析报告和正逆转换
分析报告 import json from pprint import pprint, PrettyPrinterdef analyze_energy_data(file_path):"""能源数据分析与结构查看函数参数:file_path (str): JSON文件路径功能:1. 加载并解析JSON数据2. 显示数据结构概览3. 交互式结构探索"""…...
Docker小游戏 | 使用Docker部署RPG网页小游戏
Docker小游戏 | 使用Docker部署RPG网页小游戏 前言一、项目介绍项目简介项目预览二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署RPG网页小游戏下载镜像创建容器检查容器状态检查服务端口安全设置四、访问RPG网页小游戏五、总结前言 随着互联网技术的不断…...
技术周总结 01.13~01.19 周日(Spring Visual Studio git)
文章目录 一、01.14 周二1.1)问题01:Spring的org.springframework.statemachine.StateMachine 是什么,怎么使用?:如何使用StateMachine: 1.2)问题02:Spring StateMachine 提供了一系列高级特性 …...
Linux中使用unzip
安装命令 yum install unzip unzip常用选项和参数 选项 说明 -q 隐藏解压过程中的消息输出 -d /path/to/directory 指定解压文件的目标目录 -P password 如果.zip文件被密码保护,使用此选项可以指定打开文件所需的密码 解压命令 unzip 要解压的压缩包unz…...
Baklib引领内容管理平台新时代优化创作流程与团队协作
内容概要 在迅速变化的数字化时代,内容管理平台已成为各种行业中不可或缺的工具。通过系统化的管理,用户能够有效地组织、存储和共享信息,从而提升工作效率和创意表达。Baklib作为一款新兴的内容管理平台,以其独特的优势和创新功…...
利用Redis实现数据缓存
目录 1 为啥要缓存捏? 2 基本流程(以查询商铺信息为例) 3 实现数据库与缓存双写一致 3.1 内存淘汰 3.2 超时剔除(半自动) 3.3 主动更新(手动) 3.3.1 双写方案 3.3.2 读写穿透方案 3.3.…...
jQuery小游戏(二)
jQuery小游戏(二) 今天是新年的第二天,本人在这里祝大家,新年快乐,万事胜意💕 紧接jQuery小游戏(一)的内容,我们开始继续往下咯😜 游戏中使用到的方法 key…...
农产品价格报告爬虫使用说明
农产品价格报告爬虫使用说明 # ************************************************************************** # * * # * 农产品价格报告爬虫 …...
xceed PropertyGrid 如何做成Visual Studio 的属性窗口样子
类似这样的,我百度了一下,发现使用Xceed 不错。使用PropertyGrid 前台代码为 <Windowx:Class"WpfApp.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.co…...
Fork/Join框架_任务分解与并行执行
1 概述 Fork/Join框架是Java 7引入的一个用于并行执行任务的框架。它特别适用于可以递归分解为多个子任务的工作,每个子任务可以独立执行,并且结果可以合并以获得最终结果。Fork/Join框架通过工作窃取(work-stealing)算法提高了多核处理器上的任务执行效率。 2 核心组件 …...
智能家居监控系统数据收集积压优化
亮点:RocketMQ 消息大量积压问题的解决 假设我们正在开发一个智能家居监控系统。该系统从数百万个智能设备(如温度传感器、安全摄像头、烟雾探测器等)收集数据,并通过 RocketMQ 将这些数据传输到后端进行处理和分析。 在某些情况下…...
详解python的单例模式
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在Python中实现单例模式有多种方法,下面我将详细介绍几种常见的实现方式。 1. 使用模块 Python的模块天然就是单例的,因为模块在第一次导…...
momask-codes 部署踩坑笔记
目录 依赖项 t2m_nlayer8_nhead6_ld384_ff1024_cdp0.1_rvq6ns 推理代码完善: 代码地址: https://github.com/EricGuo5513/momask-codes 依赖项 pip install numpy1.23 matplotlib 必须指定版本:pip install matplotlib3.3.4 t2m_nlayer…...
H3CNE-31-BFD
Bidirectional Forwarding Dection,双向转发检查 作用:毫秒级故障检查,通常结合三层协议(静态路由、vrrp、ospf、BGP等),实现链路故障快速检查。 BFD配置示例 没有中间的SW,接口downÿ…...
蓝桥备赛指南(5)
queue队列 queue是一种先进先出的数据结构。它提供了一组函数来操作和访问元素,但它的功能相对较简单,queue函数的内部实现了底层容器来存储元素,并且只能通过特定的函数来访问和操作元素。 queue函数的常用函数 1.push()函数:…...
讯飞智作 AI 配音技术浅析(一)
一、核心技术 讯飞智作 AI 配音技术作为科大讯飞在人工智能领域的重要成果,融合了多项前沿技术,为用户提供了高质量的语音合成服务。其核心技术主要涵盖以下几个方面: 1. 深度学习与神经网络 讯飞智作 AI 配音技术以深度学习为核心驱动力&…...
MySQL(高级特性篇) 14 章——MySQL事务日志
事务有4种特性:原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证(1)REDO LOG称为重做日志,用来保证事务的持久性(2)UNDO LOG称为回…...
openRv1126 AI算法部署实战之——TensorFlow TFLite Pytorch ONNX等模型转换实战
Conda简介 查看当前系统的环境列表 conda env list base为基础环境 py3.6-rknn-1.7.3为模型转换环境,rknn-toolkit版本V1.7.3,python版本3.6 py3.6-tensorflow-2.5.0为tensorflow模型训练环境,tensorflow版本2.5.0,python版本…...
【Redis】常见面试题
什么是Redis? Redis 和 Memcached 有什么区别? 为什么用 Redis 作为 MySQL 的缓存? 主要是因为Redis具备高性能和高并发两种特性。 高性能:MySQL中数据是从磁盘读取的,而Redis是直接操作内存,速度相当快…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
