Effective Objective-C 2.0 读书笔记—— 消息转发

Effective Objective-C 2.0 读书笔记—— 消息转发

文章目录

  • Effective Objective-C 2.0 读书笔记—— 消息转发
    • 前言
    • 消息转发机制概述
      • 动态方法解析
        • 处理`@dynamic`的属性
        • 用于懒加载
      • 消息转发
        • 快速消息转发
        • 完整消息转发
    • 总结

前言

在前面我学习了关联对象和objc_msgSend的相关内容,初步了解了OC的动态机制,在我们的objc_msgSend的执行操作之中,我们提到了,如果对象接受了无法解读的消息之后,就会进行消息转发。那么什么消息可以被理解呢?最基本的就是,我们的程序要实现对应的方法,由于OC动态语言的特性,我们在编译期的时候仍可以在类之中添加方法,所以当对象接受到无法解析的消息时就会启动消息转发机制(message forwarding)。

消息转发机制概述

消息转发一共由两种情况

  1. 动态方法解析(Dynamic Method Resolution):如果一个对象没有实现某个方法,Objective-C 会尝试在运行时为该方法动态添加实现。
  2. 消息转发(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 是运行时函数,允许你动态地为某个类添加方法。
  • 它的参数依次是:
    1. self:要为哪个类添加方法,通常是当前类。
    2. selector:方法选择器,表示要添加的方法的名字。
    3. (IMP)autoDictionarySetter:方法实现,IMP 是指向方法实现的指针,这里是 autoDictionarySetter函数的指针。
    4. "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,第二次就会直接调用经过动态绑定的方法

image-20250127235648523

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)会介入,负责将消息转发给目标对象,执行相应的方法。

然而这样实现出来的方法与“备援接收者” 方案所实现的方法等效,所以很少有人采用这么简单的实现方式。

流程图:

image-20250128122209665

总结

  • 若对象无法响应某个选择子,则进人消息转发流程。
  • 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
  • 对象可以把其无法解读的某些选择子转交给其他对象来处理。
  • 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。

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

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

相关文章

Hive:struct数据类型,内置函数(日期,字符串,类型转换,数学)

struct STRUCT&#xff08;结构体&#xff09;是一种复合数据类型&#xff0c;它允许你将多个字段组合成一个单一的值, 常用于处理嵌套数据&#xff0c;例如当你需要在一个表中存储有关另一个实体的信息时。你可以使用 STRUCT 函数来创建一个结构体。STRUCT 函数接受多个参数&…

嵌入式知识点总结 Linux驱动 (二)-uboot bootloader

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.什么是bootloader&#xff1f; 2.Bootloader的两个阶段 3.uboot启动过程中做了哪些事&#xff1f; 4.uboot和内核kernel如何完成参数传递&#xff1f; 5.为什么要给内核传递…

实战技巧:如何快速提高网站的收录比例?

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/28.html 快速提高网站的收录比例是网站优化中的重要目标之一。以下是一些实战技巧&#xff0c;可以帮助你实现这一目标&#xff1a; 一、内容优化 高质量原创内容&#xff1a; 确保网站内…

Unbutu虚拟机+eclipse+CDT编译调试环境搭建

问题1: 安装CDT&#xff0c;直接Help->eclipse Market space-> 搜cdt , install&#xff0c;等待重启即可. 问题2&#xff1a;C变量不识别vector ’could not be resolved 这是库的头文件没加好&#xff0c;右键Properties->C Build->Enviroment&#xff0c;增加…

碳化硅MOSFET相对IGBT和超结MOSFET出现价格倒挂预示着什么

碳化硅&#xff08;SiC&#xff09;MOSFET相对于IGBT和超结MOSFET出现价格倒挂&#xff08;即SiC MOSFET单价低于传统硅基器件&#xff09;&#xff0c;这一现象反映了化合物半导体产业的深刻变革&#xff0c;并预示着技术、市场和产业链格局的多重演变。倾佳电子杨茜从技术突破…

openRv1126 AI算法部署实战之——TensorFlow TFLite Pytorch ONNX等模型转换实战

Conda简介 查看当前系统的环境列表 conda env list base为基础环境 py3.6-rknn-1.7.3为模型转换环境&#xff0c;rknn-toolkit版本V1.7.3&#xff0c;python版本3.6 py3.6-tensorflow-2.5.0为tensorflow模型训练环境&#xff0c;tensorflow版本2.5.0&#xff0c;python版本…

人工智能丨基于机器学习的视觉 CV 处理技术

从自动驾驶汽车到面部识别系统&#xff0c;CV无处不在&#xff0c;赋予计算机“看”的能力。无论是图像处理、模式识别&#xff0c;还是视频分析&#xff0c;机器学习都是推动这些技术进步的核心动力。这篇文章将深入探讨基于机器学习的计算机视觉处理技术&#xff0c;包括它的…

理解PLT表和GOT表

1 简介 现代操作系统都是通过库来进行代码复用&#xff0c;降低开发成本提升系统整体效率。而库主要分为两种&#xff0c;一种是静态库&#xff0c;比如windows的.lib文件&#xff0c;macos的.a&#xff0c;linux的.a&#xff0c;另一种是动态库&#xff0c;比如windows的dll文…

【java学习笔记】@Autowired注解 使用方法和作用 | 配合@Component注解使用 | IOC控制反转

原本在类中&#xff0c;要用什么对象&#xff0c;就直接new一个对象。这种原始的方式 是由应用本身去控制实例的。 用了Autowired注解后&#xff0c;就相当于把实例&#xff08;对象&#xff09;的控制权 交给外部容器来统一管理&#xff08;降低耦合&#xff09;。&#xff08…

LabVIEW无线齿轮监测系统

本案例介绍了基于LabVIEW的无线齿轮监测系统设计。该系统利用LabVIEW编程语言和改进的天牛须算法优化支持向量机&#xff0c;实现了无线齿轮故障监测。通过LabVIEW软件和相关硬件&#xff0c;可以实现对齿轮箱振动信号的采集、传输和故障识别&#xff0c;集远程采集、数据库存储…

SpringBoot+Vue的理解(含axios/ajax)-前后端交互前端篇

文章目录 引言SpringBootThymeleafVueSpringBootSpringBootVue&#xff08;前端&#xff09;axios/ajaxVue作用响应式动态绑定单页面应用SPA前端路由 前端路由URL和后端API URL的区别前端路由的数据从哪里来的 Vue和只用三件套axios区别 关于地址栏url和axios请求不一致VueJSPS…

jQuery小游戏(一)

jQuery小游戏&#xff08;一&#xff09; 嘻嘻&#xff0c;今天我们来写个jquery小游戏吧 首先&#xff0c;我们准备一下写小游戏需要准备的佩饰&#xff0c;如果&#xff1a;图片、音乐、搞怪的小表情 这里我准备了一些游戏中需要涉及到的图片 游戏中使用到的方法 eval() 函…

H3CNE-28-VRRP

虚拟网关冗余协议&#xff0c;Virtual Router Redundancy Protocotol 三层网关冗余技术对用户网关做冗余 VRRP配置示例 接口IP配置&#xff0c;略。 R1&#xff1a; int g0/0vrrp vrid 1 virtual 192.168.1.254vrrp vrid 1 priority 105 # 1-254,越大越优先R2&#xff1a; …

私有包上传maven私有仓库nexus-2.9.2

一、上传 二、获取相应文件 三、最后修改自己的pom文件

Alfresco Content Services dockerCompose自动化部署详尽操作

Alfresco Content Services docker社区部署文档 Alfresco Content Services简介 官方说明书 https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Using/Content/Folder-rules/Defining-…

rust feature h和 workspace相关知识 (十一)

feature 相关作用和描述 在 Rust 中&#xff0c;features&#xff08;特性&#xff09; 是一种控制可选功能和依赖的机制。它允许你在编译时根据不同的需求启用或禁用某些功能&#xff0c;优化构建&#xff0c;甚至改变代码的行为。Rust 的特性使得你可以轻松地为库提供不同的…

【Python-办公自动化】实现自动化输出json数据类型的分析报告和正逆转换

分析报告 import json from pprint import pprint, PrettyPrinterdef analyze_energy_data(file_path):"""能源数据分析与结构查看函数参数:file_path (str): JSON文件路径功能:1. 加载并解析JSON数据2. 显示数据结构概览3. 交互式结构探索"""…

麒麟操作系统服务架构保姆级教程(十四)iptables防火墙四表五链和防火墙应用案例

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 防火墙在运维工作中有着不可或缺的重要性。首先&#xff0c;它是保障网络安全的关键防线&#xff0c;通过设置访问控制规则&#xff0c;可精准过滤非法网络流量&#xff0c;有效阻挡外部黑客攻击、恶…

力扣25.k个一组翻转链表

给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值&…

Midjourney基础-常用修饰词+权重的用法大全

用好修饰词很关键 Midjourney要用除了掌握好提示词的写法&#xff0c;按照上一篇《做Midjourney最好图文教程-提示词公式以及高级参数讲解》画面主体 场景氛围 主体行为 构图方式 艺术风格 图像质量。 要画出有质感的内容我们必须要掌握好“修饰词”&#xff0c;这些修饰…