Demo结构预览

在分析方法的本质之前,先看一下项目的结构:
继承关系:Student继承自Person,Person继承自NSObject,这里只放了.h文件,在.m文件中分别实现了对应的方法,并且输出当前的方法名:NSLog(@”%s”,func);

  1. // NSObject+Function
  2. @interface NSObject (Function)
  3. -(void)nsobjectA;// 实例方法
  4. +(void)nsobjectB;// 类方法
  5. @end
  6. // Person
  7. @interface Person : NSObject
  8. -(void)personA;// 实例方法
  9. +(void)personB;// 类方法
  10. @end
  11. // Student
  12. @interface Student : Person
  13. -(void)studentA;// 实例方法
  14. +(void)studentB;// 类方法
  15. @end

clang编译查看底层源码

在main.m中定义一个run()的C函数,并在main方法中初始化Student类调用一个studentA()方法:

  1. // c函数
  2. void run(){
  3. }
  4. int main(int argc, const char * argv[]) {
  5. @autoreleasepool {
  6. Student *student = [Student new];
  7. [student studentA];
  8. }
  9. return 0;
  10. }

然后将这个main.m文件编译成cpp文件:
将终端进入main.m文件所在目录,执行 clang -rewrite-objc main.m -o main.cpp 指令,打开这个main.cpp文件,直接拉到最底部:
屏幕快照 2020-04-01 18.07.14.png
run()函数就是C语言写的,通过clang编译之后,还是以前的样子,
那么[student studentA] 变成了:
((void ()(id, SEL))(void )objc_msgSend)((id)student, sel_registerName(“studentA”));
去掉格式和符号精简来看:
objc_msgSend(student, sel_registerName(“studentA”));
OC方法调用的底层本质就是 objc_msgSend(id _Nullable self, SEL _Nonnull op, …) 函数实现消息发送。

objc_msgSend简析

objc_msgSend是用汇编实现的

objc_msgSend使用汇编语言实现的,原因总结有两点:
1、在C语言中不可能通过写一个函数来保留位置的参数并且跳转到一个任意的函数指针。C语言没有满足做这件事情的必要特性;
2、objc_msgSend的速度,用汇编来实现,足够快,汇编最接近机器语言,容易被识别。

objc_msgSend路径

objc_msgSend有一个快速路径使用汇编实现的,还有一个慢速路径是用C语言实现的。

objc_msgSend快速路径实现流程

这个是用汇编实现的,这里我就直接把老师的总结流程贴出来(汇编我不懂哈哈哈哈):
屏幕快照 2020-04-01 18.20.12.png
在调用方法的时候,系统首先就会去class的cache中查找是否缓存过这个方法,然后直接拿来用,这个查找的过程,就是上边提到的快速查找;
总结一下就是:进入_objc_msgSend中,找到消息接受者进行判断,通过各种偏移找到isa和class,然后在class中找到cache;在cache中遍历找bucket,返回imp,找不到的话,就开始了慢速查找流程(C语言实现);

慢速查找流程

既然缓存里边没有,那么去哪儿找?我们知道方法存在类中,类方法存在元类中,所以这就要去它原来的地方去找,详细请看方法的查找流程

objc_msgSend在OC中的应用演示

既然方法调用的本质就是objc_msgSend,那么在OC中,也可以这样用。

objc_msgSend的配置坑点

在项目的TARGETSBuild Settings中搜索objc_msgSend如下图:
屏幕快照 2020-04-01 22.00.05.png
如图的设置需要设置成NO,如果设置成YES的话就会报错:
屏幕快照 2020-04-01 22.09.20.png
这个是个坑,注意一下。

代码演示

下边看一下用objc_msgSend来实现方法的调用:
屏幕快照 2020-04-01 22.12.40.png
objc_msgSend(id _Nullable self, SEL _Nonnull op, …):
第一个参数:接收消息的接受者(方法的调用者),如果查找实例方法,就需要传个对象;如果查找的是类方法,就需要传个类,就等同于[student studetA];[Student studentB];
第二个参数:SEL,这里sel_registerName(“”);@selector(xx)的写法是一个意思,这两个写法返回的都是SEL
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, …):
第一个参数:objc_super类型指针,构造objc_super对象,需要一个receiver:接受者(方法的调用者);一个super_class:父类,这个调用的是实例方法,需要传类,如果是类方法,需要传元类(在父类的类中找实例方法,在父类的元类中找类方法)

总结

OC方法调用的底层本质就是 objc_msgSend(id _Nullable self, SEL _Nonnull op, …) 函数实现消息发送。在调用方法的时候先通过快速查找方式(通过汇编objc_msgSend实现)在cache缓存中查找,如果找不到则通过C语言函数开始慢速查找。在下面的博客中,会详细分析慢速查找流程—-方法的查找流程