iOS - Runtime-消息机制-objc_msgSend()

iOS - Runtime-消息机制-objc_msgSend()

前言

本章主要介绍消息机制-objc_msgSend的执行流程,分为消息发送动态方法解析消息转发三个阶段,每个阶段可以做什么。还介绍了super的本质是什么,如何调用的

1. objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数的调用

objc_msgSend的执行流程可以分为3大阶段

  • 消息发送

  • 动态方法解析

  • 消息转发

1.1 消息发送

  • 如果是从class_rw_t中查找方法

    1. 已经排序的,二分查找
    2. 没有排序的,遍历查找
  • receiver通过isa指针找到receiverClass

  • receiverClass通过superclass指针找到superClass

1.2 动态方法解析

1.2.1 开发者可以实现以下方法,来动态添加方法实现
  • +resolveInstanceMethod: -----用于 实例方法
  • +resolveClassMethod: -----用于类方法
1.2.2 动态解析过后,会重新走“消息发送”的流程
  • “从receiverClass的cache中查找方法”这一步开始执行
1.2.3 示例

ZSXPerson类有test方法,但是方法实现注释掉了

@interface ZSXPerson : NSObject- (void)test;@end@implementation ZSXPerson//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}@end

此时我们调用 test 方法发就会报错unrecognized selector sent to instance

动态方法解析阶段给类对象增加方法实现

- (void)other {NSLog(@"ZSXPerson - %s", __func__);
}+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(test)) {// 获取 qitafangfaMethod method = class_getInstanceMethod(self, @selector(other));// 动态添加 test 方法的实现class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));// 返回YES代表有动态添加方法return YES;}return [super resolveInstanceMethod:sel];
}

将方法实现other设置为test的方法实现,这时候可以看到不会报错了,而是执行了- (void)other方法

1.2.3.1 类方法

动态方法解析类方法也是类似的,只不过用的是+resolveClassMethod:方法,并且class_addMethod应该给元类对象添加方法。使用object_getClass(self)获取元类对象

@interface ZSXPerson : NSObject- (void)test;+ (void)test1;@end@implementation ZSXPerson//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}//+ (void)test1 {
//    NSLog(@"ZSXPerson - %s", __func__);
//}- (void)other {NSLog(@"ZSXPerson - %s", __func__);
}+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(test)) {// 获取 qitafangfaMethod method = class_getInstanceMethod(self, @selector(other));// 动态添加 test 方法的实现class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));// 返回YES代表有动态添加方法return YES;}return [super resolveInstanceMethod:sel];
}+ (BOOL)resolveClassMethod:(SEL)sel {if (sel == @selector(test1)) {// 获取 qitafangfaMethod method = class_getInstanceMethod(self, @selector(other));// 动态添加 test 方法的实现class_addMethod(object_getClass(self), sel, method_getImplementation(method), method_getTypeEncoding(method));// 返回YES代表有动态添加方法return YES;}return [super resolveClassMethod:sel];
}@end

main.m

int main(int argc, const char * argv[]) {@autoreleasepool {
//        ZSXPerson *person = [[ZSXPerson alloc] init];
//        [person test];[ZSXPerson test1];}return 0;
}

执行结果

1.3 消息转发

如果动态方法解析阶段没有处理,回来到消息转发阶段

  • 首先来到forwardingTargetForSelector:方法,该方法中可以重新返回一个消息接收者,程序将会重新执行objc_msgSend()方法,此时消息时发送给新的接受者
  • 如果forwardingTargetForSelector:方法没有处理,会来到methodSignatureForSelector:方法,该方法可以返回一个方法签名,返回后,程序会继续调用forwardInvocation:方法。如果methodSignatureForSelector:方法也没处理,程序就抛出异常
  • forwardInvocation:方法中,开发者可以自定义任何逻辑
  • 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
1.3.1 forwardingTargetForSelector:

新建一个ZSXCat类,该类实现了- (void)test方法

@interface ZSXCat : NSObject@end@implementation ZSXCat- (void)test {NSLog(@"ZSXCat - %s", __func__);
}@end

实现forwardingTargetForSelector:方法,将消息接受者转发给ZSXCat对象

@interface ZSXPerson : NSObject- (void)test;@end@implementation ZSXPerson//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}//+ (void)test1 {
//    NSLog(@"ZSXPerson - %s", __func__);
//}- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(test)) {return [[ZSXCat alloc] init];}return [super forwardingTargetForSelector:aSelector];
}@end

运行结果: 调用了ZSXCat- (void)test方法

1.3.2 methodSignatureForSelector:
@implementation ZSXPerson//- (void)test {
//    NSLog(@"ZSXPerson - %s", __func__);
//}//- (id)forwardingTargetForSelector:(SEL)aSelector {
//    if (aSelector == @selector(test)) {
//        return [[ZSXCat alloc] init];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if (aSelector == @selector(test)) {return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];}return [super methodSignatureForSelector:aSelector];
}// NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
// anInvocation.target  方法调用者
// anInvocation.selector  方法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"---- forwardInvocation");
}@end

返回方法签名,来到forwardInvocation:继续执行

2. 拓展

2.1 forwardInvocation:自定义逻辑

动态方法解析阶段,+resolveClassMethod:方法是可以给消息接受者动态增加一个`方法实现

消息转发阶段,forwardingTargetForSelector:方法是可以重新返回一个消息接受者,相当于是让另一个人来处理这个方法。

但是,来到methodSignatureForSelector:方法后,可以使用方法签名自定义更复杂的业务

2.1.1 方法签名阶段的其他用法
把方法调用 转发给ZSXCat对象
- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"---- forwardInvocation");// 把方法调用 转发给ZSXCat对象[anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
}
使用参数

方法增加age参数- (void)test:(int)age

调用时传入参数:

int main(int argc, const char * argv[]) {@autoreleasepool {ZSXPerson *person = [[ZSXPerson alloc] init];[person test:10];}return 0;
}

方法签名使用参数

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if (aSelector == @selector(test:)) {return [NSMethodSignature signatureWithObjCTypes:"i24@0:8i16"];}return [super methodSignatureForSelector:aSelector];
}// NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
// anInvocation.target  方法调用者
// anInvocation.selector  方法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"---- forwardInvocation");// 把方法调用 转发给ZSXCat对象
//    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];// 取出参数。参数顺序:receiver、selector、other argumentsint age;[anInvocation getArgument:&age atIndex:2];NSLog(@"age is %d", age + 10);
}

打印结果:

2.2 消息转发 - 类方法

在处理消息转发的时候,会发现如果forwardingTargetForSelectormethodSignatureForSelector方法,使用+开头写法时代码提示没有这俩方法,所以有的人认为消息转发不支持类方案

其实是支持的,把方法的-号改成+即可:

@interface ZSXPerson : NSObject- (void)test:(int)age;+ (void)test1;@end
@implementation ZSXCat- (void)test {NSLog(@"ZSXCat - %s", __func__);
}+ (void)test1 {NSLog(@"ZSXCat - %s", __func__);
}@end

main.m

int main(int argc, const char * argv[]) {@autoreleasepool {
//        ZSXPerson *person = [[ZSXPerson alloc] init];
//        [person test:10];[ZSXPerson test1];}return 0;
}

ZSXPerson.m

@implementation ZSXPerson//- (void)test:(int)age {
//    NSLog(@"ZSXPerson - %s", __func__);
//}- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(test)) {return [[ZSXCat alloc] init];}return [super forwardingTargetForSelector:aSelector];
}//+ (id)forwardingTargetForSelector:(SEL)aSelector {
//    if (aSelector == @selector(test1)) {
//        return [ZSXCat class];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if (aSelector == @selector(test:)) {return [NSMethodSignature signatureWithObjCTypes:"i24@0:8i16"];}return [super methodSignatureForSelector:aSelector];
}+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if (aSelector == @selector(test1)) {return [NSMethodSignature signatureWithObjCTypes:"v@:"]; //v16@0:8 可简写为 v@:}return [super methodSignatureForSelector:aSelector];
}// NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
// anInvocation.target  方法调用者
// anInvocation.selector  方法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"---- forwardInvocation");
//    // 把方法调用 转发给ZSXCat对象
//    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];// 取出参数。参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"age is %d", age + 10);
}+ (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"---- forwardInvocation");// 把方法调用 转发给ZSXCat对象[anInvocation invokeWithTarget:[ZSXCat class]];
}@end

2.3 super

2.3.1 示例代码

ZSXStudent继承于ZSXPersonZSXPerson继承于NSObject。如下代码打印结果是什么

ZSXPerson.h

@interface ZSXPerson : NSObject@end

ZSXStudent.h

@interface ZSXStudent : ZSXPerson@end

ZSXStudent.m

@implementation ZSXStudent- (instancetype)init {if (self = [super init]) {NSLog(@"[self class] - %@", [self class]);NSLog(@"[self superclass] - %@", [self superclass]);NSLog(@"--------------------------------");NSLog(@"[super class] - %@", [super class]);NSLog(@"[super superclass] - %@", [super superclass]);}return self;
}@end

打印结果: 示例中,比较容易混淆的是[super class][super superclass],他们的打印结果和[self class][self superclass]一样的。

2.3.2 super本质

super调用时,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数

  • struct objc_super
  • SEL

objc_super结构体:

  • receiver是消息接收者
  • super_class是第一个要搜索的类

superself相比,它们消息接受者都是selfsuper会多传一个super_class,表示从super_class开始查找。

2.3.3 分析

ZSXStudent中,执行[super class],相当于执行这句:

objc_msgSendSuper({self, [ZSXPerson class]}, @selector(class));

表示:

  • 消息接收者:self
  • ZSXPerson类对象开始
  • 查找调用class方法

class方法存在于NSObject中的,此时不管从ZSXStudent开始查找,还是从ZSXPerson开始查找,最终都到NSObject才找到方法

class方法实现如下:

[super class][self class]他们的消息接受者都是self,也就是ZSXStudent,因此他们打印结果都是ZSXStudentsuperclass则都是ZSXPerson

@oubijiexi

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

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

相关文章

阿里云实时计算Flink的产品化思考与实践【上】

摘要:本文整理自阿里云高级产品专家黄鹏程和阿里云技术专家陈婧敏在 FFA 2023 平台建设专场中的分享。内容主要为以下五部分: 阿里云实时计算 Flink 简介产品化思考产品化实践SQL 产品化思考及实践展望 该主题由黄鹏程和陈婧敏共同完成,前半程…

java调用jacob进行文件转换ppt转pdf或者png

java调用jacob进行文件转换ppt转pdf或者png 前情提要 最近项目上,遇到一个复杂的ppt,最终要求是要将ppt每一页转成图片原本这个是不难,网上一搜一大堆案例,外加我本身也比较精通aspose,那还不是分分钟搞定。结果就是…

Django 中间件

【一】Django框架之生命周期流程图 【二】介绍 【1】概述 Django 中的中间件(Middleware)是一个轻量级、底层的“插件”系统,用来全局地改变 Django 的输入或输出。每个中间件组件负责处理特定的全局任务,例如处理会话、处理跨站…

【有限状态机】- FSM详细讲解 【附Autoware有限状态机模型代码讲解】

参考博客: (1)FSM(有限状态机) (2)关于有限状态机(FSM)的一些思考 (3)状态设计模式 1 状态机简介 有限状态机FSM:有限个状态以及在这些状态之间的转移和动作…

2024年最新最全Vue3开源后台管理系统复盘总结

在现代前端开发中,搭建一个高效、灵活、易用的后台管理系统并不容易。然而,Vue3 的出现为我们提供了一个备受瞩目的选择。作为一个现代化的前端框架,Vue3 具有众多优点,能够帮助开发者快速搭建企业级中后台产品原型。 今天&#…

iphoneX系统的参数

1. 2. 3. 4. 5.相关的网址信息 Apple iPhone X 規格、价格和评论 | Kalvo Apple iPhone X 規格、价格和评论 | Kalvo

UOS、Linux下的redis的详细部署流程(适用于内网)

提示:适用于Linux以及UOS等内外网系统服务器部署。 文章目录 一.上传离线包二.部署基本环境三.解压并安装redis四.后台运行redis五.uos系统可能遇到的问题六.总结 一.上传离线包 1.自己去Redis官网下载适配自己部署系统的redis安装包。 2.通过文件传输工具&#xf…

Rust使用原始字符串字面量实现Regex双引号嵌套双引号正则匹配

rust使用Regex实现正则匹配的时候,如果想实现匹配双引号,就需要使用原始字符串字面量,不然无法使用双引号嵌套的。r#"..."# 就表示原始字符串字面量。 比如使用双引号匹配: use regex::Regex;fn main() {println!(&qu…

快速幂算法在Java中的应用

引言: 在计算机科学和算法领域中,快速幂算法是一种用于高效计算幂运算的技术。在实际编程中,特别是在处理大数幂运算时,快速幂算法能够显著提高计算效率。本文将介绍如何在Java中实现快速幂算法,并给出一些示例代码和应…

151 shell编程,正则表达式,在C语言中如何使用正则表达式

零,坑点记录:bash 和 dash 的区别,导致的坑点 查看当前用的shell 是啥,用的是/bin/bash hunandedehunandede-virtual-machine:~$ echo $SHELL /bin/bash 当shell 脚本运行的时候(后面会学到方法,这里是最…

全局UI方法-弹窗一警告弹窗(AlertDialog)

1、描述 显示警告弹窗组件,可设置文本内容与响应回调。 2、属性 名称参数类型参数描述showAlertDialogParamWithConfirm | AlertDialogParamWithButtons定义并显示AlertDialog组件。 2.1、AlertDialogParamWithConfirm对象说明: 参数名称参数类型必填…

『Apisix安全篇』探索Apache APISIX身份认证插件:从基础到实战

🚀『Apisix系列文章』探索新一代微服务体系下的API管理新范式与最佳实践 【点击此跳转】 📣读完这篇文章里你能收获到 🛠️ 了解APISIX身份认证的重要性和基本概念,以及如何在微服务架构中实施API安全。🔑 学习如何使…

FreeRTOS(三)

第二部分 事件组 一、事件组的简介 1、事件 事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。其实事件组的本质就是一个整数(16/32位)。可以是一个事件发生唤醒一个任务&#xff…

ClickHouse初体验

1.clickHouse是啥? ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库(DBMS),使用 C语言编写,主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告 2.clickHouse的特点 2.1列式存储 对于列的聚合&…

城市内涝排水新模式:慧天[HTWATER]

慧天[HTWATER]软件:慧天排水数字化分析平台针对城市排水系统基础设施数据管理的需求,以及水文、水力及水质模拟对数据的需求,实现了以数据库方式对相应数据的存储。可以对分流制排水系统及合流制排水系统进行地表水文、管网水力、水质过程的模…

Transformers 直观解释——不仅是如何工作,而且为什么工作得这么好

输入序列如何到达Attention模块 注意力模块存在于编码器堆栈中的每个编码器中,以及解码器堆栈中的每个解码器中。我们将首先放大编码器的注意力。 Attention in the Encoder: 举个例子,假设我们正在研究一个英语到西班牙语的翻译问题&…

【旅游】泉州攻略v1.0.0

一、泉州古城 泉州市距离深圳大约520公里,从深圳北站出发,高铁大约3小时30分。 到达泉州西站后,往东南方向大约8公里,就可以到达主要的旅游景点泉州古城。 古城很适合使用一天玩耍,核心路线如下: 一路的景…

C++ STL教程

C STL教程 文章目录 C STL教程1.1 std::vector1.1.1vector的定义1.1.2vector容器的初始化1.1.3vector容器内元素的访问和修改1.1.4vector中的常用函数 1.2 std::string1.2.1string的定义1.2.2string的初始化1.2.3string中元素的访问和修改1.2.4string中连接字符串1.2.5string中…

AtCoder Beginner Contest 337 A - E

A - Scoreboard 大意 高桥队和青木队进行了场比赛&#xff0c;给出每场比赛中高桥队和青木队的积分&#xff0c;问最后谁总分更高或平局。 思路 统计总分比较即可。 代码 #include<iostream> using namespace std; int main(){int n, a0, b0;cin >> n;while(…

介绍部署esxi8.0产品的方式

什么是esxi esxi的中文叫裸机虚拟机管理器 ESXi是由VMware公司开发的一种裸机虚拟机管理器&#xff0c;全称为VMware ESXi。 ESXi是一种虚拟化技术&#xff0c;专门设计用于在物理服务器上运行虚拟机&#xff0c;它的主要特点是能够最大限度地降低硬件配置要求并简化部署过程…