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)) {
        // 获取 qitafangfa
        Method 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)) {
        // 获取 qitafangfa
        Method 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)) {
        // 获取 qitafangfa
        Method 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 arguments
    int 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

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

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

相关文章

OceanBase中NOT EXISTS是否需要被改写

作者简介 张瑞远,曾经从事银行、证券数仓设计、开发、优化类工作,现主要从事电信级IT系统及数据库的规划设计、架构设计、运维实施、运维服务、故障处理、性能优化等工作。 持有Orale OCM,MySQL OCP及国产代表数据库认证。 获得的专业技能与认证包括 Oce…

安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 从0开始 工具操作解析【三】

同类博文; 安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 工具操作解析 安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 工具操作解析【二】-CSDN博客 回顾以往 在以前的博文简单介绍了这款工具的rom制作全程。今天针对这款工具的…

Rust 02.控制、引用、切片Slice

1.控制流 //rust通过所有权机制来管理内存,编译器在编译就会根据所有权规则对内存的使用进行 //堆和栈 //编译的时候数据的类型大小是固定的,就是分配在栈上的 //编译的时候数据类型大小不固定,就是分配堆上的 fn main() {let x: i32 1;{le…

YoloV5改进策略:Neck和Head改进|ECA-Net:用于深度卷积神经网络的高效通道注意力|多种改进方法|附结构图

摘要 本文使用ECA-Net注意力机制加入到YoloV5Neck和Head中。我尝试了多种改进方法,并附上改进结果,方便大家了解改进后的效果,为论文改进提供思路。(改进中。。。。) 论文:《ECA-Net:用于深度…

Android中运动事件的处理

1.目录 目录 1.目录 2.前言 3.程序演示 4.第二种程序示例 5.扩展 2.前言 触摸屏(TouchScreen)和滚动球(TrackBall)是 Android 中除了键盘之外的主要输入设备。如果需要使用触摸屏和滚动球,主要可以通过使用运动事…

ruoyi-nbcio-plus基于vue3的flowable的流程条件的升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

JavaScript中的继承方式详解

Question JavaScript实现继承的方式? 包含原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承和ES6 类继承 JavaScript实现继承的方式 在JavaScript中,实现继承的方式多种多样,每种方式都有其优势和适用场景。以下…

HarmonyOS(鸿蒙开发)入门篇

如果需要学习鸿蒙开发可以查看以下学习资源链接 OpenAtom OpenHarmony Develop applications - HUAWEI HarmonyOS APP 转载请注明出处HarmonyOS(鸿蒙开发)入门篇-CSDN博客,谢谢!

unity 数据的可视化

【Unity 实用插件篇】| 可视化图表插件XCharts (折线图、柱状图、饼图等)详细教学-腾讯云开发者社区-腾讯云 Package https://github.com/XCharts-Team/XCharts/releases 官方文档案例 入门教程:5分钟上手 XCharts 3.0 | XCharts (xcharts-team.github.io)

Linux 系统基础操作命令

当前市面上常见的系统:Windows、Linux、Mac OS、Android、IOS…… Linux 不太适合日常使用,但是非常适合用于开发。因此作为一个程序猿来说,Linux 都是务必要掌握的。 Linux 介绍 Linux 发行版 目前市面上比较知名的发行版有:R…

c(RGDfK)-FITC, 绿色荧光FITC标记细胞穿膜肽c(RGDfk

中文名称 :荧光标记c(RGDfk)环肽 英 文 名 :c(RGDfK)-FITC c(RGDfK(FITC)) 品 牌 :Tanshtech 单字母: c(RGD-DPhe-K(Fitc)) 三字母:Cyclo(Arg-Gly-Asp…

web学习笔记(四十六)

目录 1. path 路径模块 1.1 导入path模块 1.2 path.join()路径拼接 1.3 path.basename() 获取路径中的文件名 1.4 path.extname() 获取路径中的扩展名 2.服务器的相关概念 2.1 IP 地址 2.2 域名和域名服务器 2.3 端口号 3. http 模块 3.1使用http模块搭建服务器的步…

WIFI驱动移植实验:配置 Linux 内核

一. 简介 前面文章删除了Linux内核源码(NXP官方的kernel内核源码)自带的 WIFI驱动。 WIFI驱动移植实验:删除Linux内核自带的 RTL8192CU 驱动-CSDN博客 将正点原子提供的 rtl8188EUS驱动源码添加到 kernel内核源码中。文章如下&#xff1a…

Day59-Nginx反向代理与负载均衡算法精讲及会话保持精讲

Day59-Nginx反向代理与负载均衡算法精讲及会话保持精讲 7.nginx负载均衡调度算法7.1 什么是nginx负载均衡调度算法7.2 nginx负载均衡调度算法有哪些。 8.负载均衡后端的会话保持8.1 nginx负载均衡会话(session)保持8.2 负载均衡集群会话保持8.3 实践共享会话保持 7.nginx负载均…

《Mahjong Bump》

Mahjong Bump 类型:Tile 三消 视角:2d 乐趣点:清空杂乱快感,轻松的三合一休闲 平台:GP 时间:2021 个人职责: 所有程序部分开发 上架 GooglePlay 相关工做 针对游戏数据做出分析,讨论…

并发编程之的HashSet和HashMap的详细解析

HashSet不安全 HashSet也是线程不安全的,底层没有进行任何线程同步处理。 在hashset的源码中,底层是用hashmap实现的: 每次add的时候,把值放在了map对象中的key,而map对象的value则全部统一放一个常量: 在下…

【前端学习——js篇】6.事件模型

具体见:https://github.com/febobo/web-interview 6.事件模型 ①事件与事件流 事件(Events) 事件是指页面中发生的交互行为,比如用户点击按钮、键盘输入、鼠标移动等。在js中,可以通过事件来触发相应的操作,例如执行函数、改变…

STM32H743驱动SSD1309(3)

接前一篇文章:STM32H743驱动SSD1309(2) 三、命令说明 1. 设置命令锁定(FDh) 此双字节命令用于锁定OLED驱动器IC,不接受除其自身之外的任何命令。在输入FDh 16h(A[2]=1b)…

C语言文件操作详解

文件是什么 在我们日常使用的电脑上我们在电脑磁盘上会看到许许多多的文件夹,那里面的东西其实就是文件,为什么我们要使用文件?那是因为我们的电脑肯定会要用来存储东西的,如果没有文件,那么我们的东西都全部存放在内…

应急响应小结

应急响应的整体思路 应急响应的整体思路,就是上层有指导性原则和思想,下层有技能、知识点与工具,共同推进和保障应急响应流程的全生命周期。 原则和指导性思路 3W1H原则:3W即Who、What、Why,1H即How,做应…