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

devtools/2025/2/6 14:47:16/

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.ppmy.cn/devtools/156551.html

相关文章

【实战篇章】深入探讨:服务器如何响应前端请求及后端如何查看前端提交的数据

文章目录 深入探讨&#xff1a;服务器如何响应前端请求及后端如何查看前端提交的数据一、服务器如何响应前端请求HTTP 请求生命周期全解析1.前端发起 HTTP 请求&#xff08;关键细节强化版&#xff09;2. 服务器接收请求&#xff08;深度优化版&#xff09; 二、后端如何查看前…

4.回归与聚类算法 4.1线性回归

4.1.1 线性回归的原理 1 线性回归应用场景&#xff1a; 房价预测 销售额度预测 金融&#xff1a;贷款额度预测&#xff0c;利用线性回归以及系数分析因子 2 什么是线性回归 1&#xff09; 定义&#xff1a;利用回归方程&#xff08;函数&#xff09;对一个或者多个自变量…

大话特征工程:3.特征扩展

公元 2147 年&#xff0c;人类文明站在科技的巅峰&#xff0c;所有决策、发展甚至感知都被“全维计算网络”所掌控。这套系统以高维空间中的数据为基础&#xff0c;试图预测并塑造未来。然而&#xff0c;这场辉煌的技术革命却在悄无声息之间酿成了人类最大的危机——维数灾难。…

Unity游戏(Assault空对地打击)开发(7) 飞机坠毁后的操作

前言 本文之后基本操作不再演示。 详细操作 导入Free Fire VFX插件&#xff0c;生成火的效果。 在该文件夹下挑一个你喜欢的火&#xff0c;拖至Camera下&#xff0c;重命名为Fire。 调整一下火的位置&#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;…

Brave132 编译指南 Windows 篇:部署 Node.js(五)

1. 引言 在 Brave 浏览器 132 版本的编译过程中&#xff0c;Node.js 扮演着至关重要的角色。作为一个基于 Chrome V8 引擎构建的 JavaScript 运行时环境&#xff0c;Node.js 赋予了开发者在服务器端执行 JavaScript 代码的能力。其非阻塞、事件驱动的架构使其特别擅长构建高性…

openai agent第二弹:deepresearch原理介绍

文章目录 技术原理类似开源项目OpenDeepResearcheropen-deep-researchollama-deep-researchersmolagents的open_deep_research 参考资料 2月2日openai上线了第二个agent: deep research&#xff0c;具体功能类似24年11月google gemini发布的deep research。 技术原理 deep res…

EF Core 学习笔记(数据迁移、一对多)

程序集依赖&#xff1a;Nuget:Microsoft.EntityFrameworkCoreTools 【定义配置文件】 定义上下文配置文件&#xff0c;继承DbContext类 public class InfoManageProDbContext : DbContext{/// <summary>/// 业务系统/// </summary>public DbSet<BusinessSyste…