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

news2024/12/23 5:21:40

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

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

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

相关文章

接口自动化之 + Jenkins + Allure报告生成 + 企微消息通知推送

接口自动化之 Jenkins Allure报告生成 企微消息通知推送 在jenkins上部署好项目,构建成功后,希望可以把生成的报告,以及结果统计发送至企微。 效果图: 实现如下。 1、生成allure报告 a. 首先在Jenkins插件管理中&#x…

sqlite跨数据库复制表

1.方法1 要将 SQLite 数据库中的一个表复制到另一个数据库,您可以按照以下步骤操作: 备份原始表的SQL定义和数据: 使用 sqlite3 命令行工具或任何SQLite图形界面工具,您可以执行以下SQL命令来导出表的SQL定义和数据&#xff1a…

libVLC 视频抓图

Windows操作系统提供了多种便捷的截图方式,常见的有以下几种: 全屏截图:通过按下PrtSc键(Print Screen),可以截取整个屏幕的内容。截取的图像会保存在剪贴板中,可以通过CtrlV粘贴到图片编辑工具…

Machine Learning机器学习之K近邻算法(K-Nearest Neighbors,KNN)

目录 前言 背景介绍: 思想: 原理: KNN算法关键问题 一、构建KNN算法 总结: 博主介绍:✌专注于前后端、机器学习、人工智能应用领域开发的优质创作者、秉着互联网精神开源贡献精神,答疑解惑、坚持优质作品共…

探索MongoDB:发展历程、优势与应用场景

一、MongoDB简介 MongoDB 始于 2007 年,由 Dwight Merriman、Eliot Horowitz 和 Kevin Ryan –(DoubleClick 的主理团队)共同创立。 DoubleClick 是一家互联网广告公司(现隶属于 Google),公司团队开发并利…

前端埋点全解及埋点SDK实现方式

一、什么是埋点 所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、观看某个视频的时长等等。 埋点…

python-pytorch获取FashionMNIST实际图片标签数据集

在查看pytorch官方文档的时候,在这里链接中https://pytorch.org/tutorials/beginner/basics/data_tutorial.html的Creating a Custom Dataset for your files章节,有提到要自定义数据集,需要用到实际的图片和标签。 在网上找了半天没找到&a…

Jenkins用户角色权限管理

Jenkins作为一款强大的自动化构建与持续集成工具,用户角色权限管理是其功能体系中不可或缺的一环。有效的权限管理能确保项目的安全稳定,避免敏感信息泄露。 1、安装插件:Role-based Authorization Strategy 系统管理 > 插件管理 > 可…

java入门学习Day01

本篇文章主要是学会如何使用IDEA,和运行第一个java文件。 java环境安装:Windows下Java环境配置教程_windows java环境配置-CSDN博客 IDEA安装:IDEA 2023.2.5 最新激活码,注册码(亲测好用) - 异常教程 以上两个链接…

Docker中常见的命令行

1 docker的全部命令 docker attach #连接到正在运行中的容器 docker build #使用 Dockerfile 创建镜像 docker builder #管理builds docker builder prune #清除build缓存 docker checkpoint #管理checkpoints docker checkpoint create #从正在运行的容器创建检…

数据结构与算法 单链表的基本运算

一、实验内容 编写一个程序实现,实现单链表的各种基本运算(假设单链表的元素类型为char),并以此为基础设计一个程序完成下列功能: 初始化单链表;采用尾插法依次插入元素a,b,c&…

小米HyperOS 澎湃os机型免答题 免社区等级 秒接bl锁操作步骤解析

小米机型解锁bl 绕过社区等级5才可以解锁的限制的教程_没有五级社区怎么解锁bl-CSDN博客 上次解析了小米有些出厂不是HyperOS系统的机型绕社区等级接bl锁的操作。目前有更新出厂为HyperOS系统的机型免社区登录等级限制 免答题解锁bl的操作。而且有网友在米14 平板6sp k70这些新…

SCI一区顶刊优化算法改进:基于强化学习的神经网络算法RLNNA,你绝对没见过,非常新颖!

声明:文章是从本人公众号中复制而来,因此,想最新最快了解各类智能优化算法及其改进的朋友,可关注我的公众号:强盛机器学习,不定期会有很多免费代码分享~ 目录 神经网络优化算法NNA: 基于强化…

vue2项目设置浏览器标题title及图标logo

工作中肯定会遇到要修改网页的标题title及图标logo 一、固定设置标题方案 方法一:在vue.config.js文件,添加如下代码: chainWebpack: config > {// 配置网页标题config.plugin(html).tap((args) > {args[0].title 标题return args})…

LoadBalance 负载均衡服务调用

前身:Ribbon LB负载均衡(Load Balance)是什么 简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用),常见的负载均衡有软件Nginx,LVS,硬件 F5等 spring-cloud-starter-loadbalancer组…

Bash and a Tough Math Puzzle 线段树维护区间gcd

还是一道很不错的题目&#xff0c;很容易想到用一棵线段树来维护区间gcd 注意用倍数来剪枝就好了&#xff0c;很是一到很好的题目的 #include<iostream> #include<vector> using namespace std; const int N 5e510; int n,q; struct Segment{int l,r;int d; }tr[…

AI智聊功能支持生成旅游攻略、作文、标题优化,方便视频剪辑

在快节奏的生活中&#xff0c;我们总是需要快速、准确地获取所需信息。无论是撰写旅游攻略、作文&#xff0c;还是准备演讲稿&#xff0c;AI智聊都能为您一键生成精彩文案&#xff0c;让您的创意无限发挥&#xff01; 媒体梦工厂的AI智聊功能&#xff0c;利用先进的自然语言处…

python Flask扩展:如何查找高效开发的第三方模块(库/插件)

如何找到扩展以及使用扩展的文档 一、背景二、如何寻找框架的扩展&#xff1f;三、找到想要的扩展四、找到使用扩展的文档五、项目中实战扩展 一、背景 刚入门python的flask的框架&#xff0c;跟着文档学习了一些以后&#xff0c;想着其实在项目开发中&#xff0c;经常会用到发…

神经网络:梯度下降法更新模型参数

作者&#xff1a;CSDN _养乐多_ 在神经网络领域&#xff0c;梯度下降是一种核心的优化算法&#xff0c;本文将介绍神经网络中梯度下降法更新参数的公式&#xff0c;并通过实例演示其在模型训练中的应用。通过本博客&#xff0c;读者将能够更好地理解深度学习中的优化算法和损…

Android-Handler详解_原理解析

为了方便阅读将文章分为使用篇和源码解析两篇&#xff0c;上一篇已经写了Handler是什么、有什么、怎们用&#xff0c;这一片从源码的角度分析完整流程&#xff0c;看看Handler消息机制到底是啥原理。才疏学浅&#xff0c;如有错误&#xff0c;欢迎指正&#xff0c;多谢。 完整…