OC底层原理14-0:方法调用的本质objc_msgSend消息发送

张建 lol

前言

我们先得出结论:方法调用的本质是 objc_msgSend消息发送

本文主要目的是理解 objc_msgSend方法查找 流程

在上一篇文章中 OC底层原理13:cache-t底层原理分析 中,分析了 cache的写入流程,在写入流程之前,还有一个 cache读取流程,即 objc_msgSendcache_getImp

在分析之前,首先了解什么是 Runtime

Runtime介绍

runtime成为运行时,它区别于编译时:

  • 运行时代码跑起来,被装载到内存中 的过程,如果此时出错,则程序会崩溃,是一个 动态 阶段

  • 编译时源代码编译成机器能识别的代码 的过程,主要是对语言进行最基本的检查报错,即 词法分析、语法分析 等,是一个 静态 的阶段

runtime使用 有以下三种方式,其中三种实现方式与编译层和底层的关系如下所示:

  • 通过 OC代码,例如 [person sayNB]

  • 通过 Framework&Service,例如 isKindOfClass

  • 通过 Runtime API,例如 class_getInstanceSize

  • complier 就是我们了解的 编译器,即 LLVM,例如 OCalloc 对应底层的 objc_alloc

  • runtime system library 就是 Runtime 底层库

  • 可以通过 command + shift + 0 打开官方文档

探索方法的本质

方法的本质

OC底层原理08:isa和类关联探索 文章中,通过 clang 编译的源码,理解了 oc对象的本质,同样的,使用clang 编译 main.m -> main.cpp 文件,通过查看main函数中方法调用的实现,如下所示

  • mian.m 内实现如下代码:
1
2
3
4
// 👇main.m中
ZJPerson * person = [ZJPerson alloc];
[person sayNB];
[person sayHello];
  • 终端 clang 编译 miam.m -> main.cpp 后的代码
1
2
3
4
// 👇clang编译后的底层实现
ZJPerson * person = ((ZJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ZJPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));

总结: 通过上述代码可以看出,方法的本质 就是 objc_msgSend消息发送

验证方法本质

方法:消息(消息的接受者,消息主体)

  • 为了验证,通过 objc_msgSend 方法来完成 [person sayNB] 的调用,查看其打印是否一致
1
2
3
4
注:👇 objc_msgSend 消息发送流程是在 <objc/message.h> 系统库中
1、直接调用 `objc_msgSend`,导入头文件 `#import<objc/message.h>`
2、需要将 target --> Build Setting --> 搜索msg --> 将 enable strict checking of objc_msgSend calls 由 YES 改为 NO,将严厉的检查机制关掉,否则 `objc_msgSend` 的参数会报错
3. `sel_registerName = @selector() = NNSelectorFromString()`

  • 再次编译运行 command + R
1
2
3
ZJPerson * person = [ZJPerson alloc];
[person sayNB];
objc_msgSend(person,sel_registerName("sayNB"));

其打印结果如下,发现是一致的,所以 [person sayNB] 等价于 objc_msgSend(person,sel_registerName("sayNB"))

子类对象方法调用-执行父类的实现

  • 首先我们定义两个类 子类ZJStudent父类ZJPerson
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 父类 ZJPerson
@interface ZJPerson : NSObject
- (void)sayNB;
@end
@implementation ZJPerson
- (void)sayNB{
NSLog(@"666");
}
@end

// ZJStudent 继承父类 ZJPerson
@interface ZJStudent : ZJPerson
- (void)sayNB;
@end
@implementation ZJStudent

@end
  • main.m 中让子类调用 sayNB 方法,查看是否能输出结果
1
2
3
4
5
6
ZJPerson * person = [ZJPerson alloc];
ZJStudent * student = [ZJStudent alloc];

// 消息的接收者还是自己 - 父类 - 请你直接找我的父亲要
[student sayNB];
objc_msgSend(student,sel_registerName("sayNB"));

运行程序,查看结果

由打印结果可知,子类方法的调用,可以 执行父类方法的实现

那么子类是如何调用父类的方法实现的呢?我们往下探索

子类调用父类方法的原理

我们先说结论:我们还可以尝试让 person 的调用执行父类中的实现,通过 objc_msgSendSuper 实现

  • main.m 函数中的调用
1
2
3
4
5
6
7
8
9
10
ZJPerson * person = [ZJPerson alloc];
ZJStudent * student = [ZJStudent alloc];

struct objc_super zjsuper;
zjsuper.receiver = person; // 消息的接收者还是person
zjsuper.super_class = [ZJPerson class]; // 告诉父类是谁

// 消息的接收者还是自己 - 父类 - 请你直接找我的父亲要
[student sayNB];
objc_msgSendSuper(&zjsuper, sel_registerName("sayNB"));

进入objc_msgSendSuper内,查看一下结构

由上图可知 objc_msgSendSuper 方法中有两个参数 (结构体,sel),其结构体类型是 objc_super 定义的结构体对象,且需要指定 receiversuper_class 两个属性

  • 运行程序,查看打印结果:
1
2
3
2023-02-25 16:31:22.959805+0800 msg_seng继承父类[53476:1503775] 666
2023-02-25 16:31:22.961094+0800 msg_seng继承父类[53476:1503775] 666
Program ended with exit code: 0

由打印结果,我们发现不论是 [person sayHello] 还是 objc_msgSendSuper 都执行的是 父类 中的 sayHello 的实现,所以这里,我们可以可以猜想:方法调用、首先是在类中查找,如果类中没有找到,会到父类中查找。

总结

  • 由上面的结论我们知道 方法的调用 实际上是 消息发送objc_msgSend

  • 在 c 中,可以直接调用 函数

  • 在 OC 中,方法调用是消息发送,消息发送objc_smgSend是通过 sel方法编号 找到 imp函数指针地址 ,进而找到 内容

  • Post title:OC底层原理14-0:方法调用的本质objc_msgSend消息发送
  • Post author:张建
  • Create time:2023-02-25 16:41:30
  • Post link:https://redefine.ohevan.com/2023/02/25/OC底层原理/OC底层原理14-0:方法调用的本质objc_msgSend消息发送/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.