本文首要内容

1.objc_msgSendSuper解析
2.办法的快速查找流程
3.办法的慢速查找算法
4.办法的慢速查找流程
5.关于办法查找的测验

前语

iOS底层原理之cache底层详解中研讨了cache的底层,了解到cache是用来缓存办法的缓存办法的意图是为了使办法再次被调用时能快速呼应。往cache中缓存办法需求调用insert函数,在objc4-838.1源码的cache源码中(62行)发现,编译器从cache中调用办法要用到objc_msgsendcache_getImp,如下图。音讯发送便是runtime经过sel找到imp的进程,音讯发送在编译阶段编译器会把这个进程转化成objc_msgSend函数。因而,接下来咱们来具体了解objc_msgSend这个函数。

iOS底层原理之方法的底层探究

知识小亮点
OC:为一门动态的言语,动态言语指程序在运转进程中,能够对类、变量进行修正,能够改变其数据结构,比如能够增加或删去函数,能够改变变量的值等。在编译阶段并不知道变量的数据类型,也不清楚要调用哪些函数,只要在运转时才去检查变量的数据类型,依据函数名找到函数的完成!
C:为一门静态的言语,在编译阶段就确定了一切变量的数据类型,一起也确定了要调用的函数,不能进行动态的修正!
runtime:便是能够完成言语动态的API。runtime的2个中心:一是类的各方面的动态配置;二是音讯传递(音讯发送+音讯转发)。

一.objc_msgSendSuper解析

创立一个macOS APP工程,增加HGPerson类及对应的办法(作者所建工程HGPerson中包括实例办法study:/happy,类办法eat),在main中新建HGPerson的实例目标并调用办法(此处先只调用实例办法)。然后在main.m文件目录下,打开终端输入clang -rewrite-objc main.m,编译main.m文件得到main.cpp文件,在此文件中找到main函数。

iOS底层原理之方法的底层探究

观察编译后的代码发现,调用办法实践上是调用了objc_msgSend函数,而且这个函数包括2个默许参数,第一个参数代表音讯的接收者,第二个参数代表音讯的办法名(sel)。objc_msgSend函数会依据音讯的接收者和音讯的办法名找到这个音讯即办法的完成(imp)。假如实例办法本身有参数,编译后会跟在这2个参数后边传递。假如音讯的接收者是类目标,就会依据类目标的isa指针指向元类目标找到元类,在元类中找对应的办法,假如音讯的接收者是实例目标,就会依据实例目标的isa指针指向类目标找到类目标,在类目标中找对应的办法。 其实,咱们也能够直接调用objc_msgSend函数来调用对应办法。

iOS底层原理之方法的底层探究

知识小亮点
查看main.cpp代码知道:
objc_msgSendSuper:发音讯给目标的父类时会编译成此函数;
objc_msgSend_stret:发音讯的返回值为结构体时会编译成此函数;
objc_msgSendSuper_stret:发音讯给目标的父类的返回值为结构体时会编译成此函数;
objc_msgSend_fpret:发音讯的返回值为浮点型时会编译成此函数;

接下来咱们具体研讨objc_msgSendSuper函数。创立新工程02_objc_msgSend Super,新建类HGPerson,其间增加实例办法study,新建承继自HGPerson的子类HGTeacher,重写init办法和study办法,在main中创立HGTeacher的实例目标并调用study办法,打印成果是什么呢?

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

实践打印成果如下:[self class]在编译时会转化为调用了objc_msgSend函数,所以输出HGTeacher[super class]在编译时转化为调用了objc_msgSendSuper函数,而该函数和objc_msgSend函数仅有的差异在于找办法的时分,前者是先从父类中找办法的完成。同理,所以[super study]打印出来的成果是先去父类中寻找该办法的完成,打印出-[HGPerson study]

iOS底层原理之方法的底层探究
为了更好的了解objc_msgSendSuper函数的调用机制,咱们自己手动完成super关键字,创立类型为objc_super的结构体并为receiversuper_ class赋值,将objc_msgSendSuper函数转化objc_msgSendSuperTyped类型,然后找study办法的完成。

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

留意⚠️:上述自己完成super关键字的办法为参阅网络办法。

当把super_class变为HGTeacher.class时,调用到objc_msgSendSuper函数时又会来HGTeacher类中找study办法的完成,所以就会循环调用HGTeacher类中的study办法。

iOS底层原理之方法的底层探究
当把super_class变为NSObject.class时,调用到objc_msgSendSuper函数时会去到NSObject类中找study办法的完成,但NSObject类并没有该办法,所以会crash

iOS底层原理之方法的底层探究

总结:objc_msgSendSuper函数和objc_msgSend函数仅有的差异在于找办法的时分,前者是先从父类中找办法的完成

二.办法的快速查找流程

结合objc4-838.1源码和汇编进行调试,跟踪study办法的查找流程。在[t study]处在增加断点,运转工程到此处后,在Debug-> Debug Workflow -> Always show Disassembly打开汇编调试工具,按住Control,点击Stepinto单步调试,进入objc msgSend函数底层完成。

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

验证objc_msgSend函数便是HGTeacher目标在调用study办法。

iOS底层原理之方法的底层探究

接下来正式进入查找流程。查看objc_msgSend函数源码(arm64)发现这个函数的底层是经过汇编完成的(598行),

知识小亮点
1.为什么objc_msgSend函数底层运用汇编?首要原因是汇编比c更块,一起汇编会免去大量对局部变量的复制操作 ,参数被直接存放在寄存器中。 
2.汇编会在函数和全局变量前加一个下划线"_",在程序中往往会包括汇编和C文件,关于编译器来说两者是一样的,因而可能会出现问题,为了避免符号名的抵触在汇编中函数和全局变量前会加一个下划线"_"。如图:

iOS底层原理之方法的底层探究

第一步,判断p0(音讯接收者)是否存在,不存在则重新开始履行objc_msgSend。经过读取寄存器证明此刻的x0的确是HGTeacher目标。

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

第2步,经过p13取到isa指针,经过打印实例目标t的内存地址验证的确是其isa指针地址。再经过isa取class并保存到p16寄存器中,读取p16寄存器得到的地址的确和HGTeacher类目标的地址相同。

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

第3步,从x16中取出class移到x15中,经过x16内存平移得到cache(x11)。

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

第4步,经过cache(x11)找存储办法的buckets。经过iOS底层原理之cache底层详解中一、经过源码剖析cache的缓存内容的办法调试查找。

iOS底层原理之方法的底层探究

第5步,假如在cache中找到buckets,在buckets中找到对应的sel,就会调用cacheHit;假如没有找到,就会调用objc_msgSend_uncached函数。

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

总结:办法(实例办法和类办法)的快速查找
objc_msgSend(receiver,sel,<其他参数>(办法的参数))
(1)判断receiver是否存在;
(2)经过receiverisa指针找到对应的class;
(3)class内存平移找到cache;
(4)经过cache找到存储办法的buckets;
(5)遍历buckets看缓存中是否存在sel办法;
(6)假如buckets中缓存有sel办法,会调用cacheHit(缓存射中),然后会调用imp;
(7)假如buckets中缓存没有sel办法,会调用objc_msgSend_uncached函数(objc-msg-arm64->761行).

三.办法的慢速查找算法

上一节中,假如buckets中缓存没有sel办法,会调用objc_msgSend_uncached函数.现在来剖析此函数。找到objc_msgSend_uncached函数源码,履行MethodTableLookup(objc-msg-arm64->735行),跳转到lookUpImpOrForward(objc_runtime_new->6446行)函数。

iOS底层原理之方法的底层探究
iOS底层原理之方法的底层探究

lookUpImpOrForward函数完成中,如下部分是系统为调用此函数做的准备工作。

iOS底层原理之方法的底层探究
接着,再一次从cache里边取找imp,这是误了避免多线程操作时刚好调用办法,此刻缓存进来了。假如在cache还是找不到,就去办法列表中查找。

iOS底层原理之方法的底层探究

终究到类的办法列表中查找,查找办法为二分法查找!

iOS底层原理之方法的底层探究

iOS底层原理之方法的底层探究

经过遍历办法列表,运用二分法查找算法查找,成果可能为查找到或未查找到,如下图:

iOS底层原理之方法的底层探究

四.办法的慢速查找流程

三.办法的慢速查找算法中假如从办法类表中查找到,履行done,调用log_and_fill_cache函数,这个函数中完成了将该办法插入到缓存中。

iOS底层原理之方法的底层探究
iOS底层原理之方法的底层探究
iOS底层原理之方法的底层探究

假如在办法列表中也没找到,就会将当时类赋为父类去父类中找,先到父类的缓存中找,找不到就会再次进入for (unsigned attempts = unreasonableClassCount() ;;) 循环到父类的办法列表中查找。

iOS底层原理之方法的底层探究

假如在父类的办法列表也没找到就到父类的父类查找,直到父类为nil,假如还没找到,就调用forward_imp(办法转发)。

总结:办法(实例办法和类办法)的慢速查找
lookUpImpOrForward函数
(1)先在当时类的methodList中查找,假如找到会进行缓存; (2)在当时类的methodList中没找到,去父类的cache中查找; (3)在父类的cache中没找到就会到父类的methodList中查找; (4)直到父类为nil,假如还没找到,就会调用forward_imp,即音讯的转发

五.关于办法查找的测验

创立一个新工程03_办法查找测验,新建承继于NSObjectHGPerson类,没有任何办法,增加NSObject的分类NSObject+Test,在其间增加一个test办法。在main.m中运用HGPerson调用test办法,运转成果是怎样的呢?会崩溃吗?答案是不会崩溃,而是会正常调用test办法正常打印成果。这其实便是验证上述办法查找的一个进程:先在当时类查找,当时类找不到就去父类,直到父类存在办法及其完成!一起阐明NSObject是万类之主

iOS底层原理之方法的底层探究

遗留问题

当直到父类为nil时仍然没有找到该办法,就会进行音讯转发,这个音讯转发是个什么进程呢?

本文总结

1.先在当时类查找,当时类找不到就去父类,直到(NSObject的父类)父类为nil,假如还没找到,就调用forward_imp(办法转发);
2.一切的办法查找进程都是为了查找到办法的完成,而不只是办法声明;
3.办法的查找进程中并没有对实例办法和类办法进行区分;
4.子类调用父类办法,其办法会缓存在子类中。

有任何问题,欢迎各位谈论指出!觉得博主写的还不错的麻烦点个赞喽