本文首要内容
1.objc_msgSendSuper解析
2.办法的快速查找流程
3.办法的慢速查找算法
4.办法的慢速查找流程
5.关于办法查找的测验
前语
iOS底层原理之cache底层详解中研讨了cache
的底层,了解到cache是用来缓存办法的
,缓存办法的意图
是为了使办法再次被调用时能快速呼应。往cache
中缓存办法需求调用insert
函数,在objc4-838.1
源码的cache
源码中(62行)发现,编译器从cache
中调用办法要用到objc_msgsend
、cache_getImp
,如下图。音讯发送便是runtime
经过sel
找到imp
的进程,音讯发送在编译阶段编译器会把这个进程转化成objc_msgSend
函数。因而,接下来咱们来具体了解objc_msgSend
这个函数。
知识小亮点
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
函数。
观察编译后的代码发现,调用办法实践上是调用了objc_msgSend
函数,而且这个函数包括2个默许参数,第一个参数代表音讯的接收者,第二个参数代表音讯的办法名(sel)。objc_msgSend
函数会依据音讯的接收者和音讯的办法名找到这个音讯即办法的完成(imp)。假如实例办法本身有参数,编译后会跟在这2个参数后边传递。假如音讯的接收者是类目标,就会依据类目标的isa指针指向元类目标找到元类,在元类中找对应的办法,假如音讯的接收者是实例目标,就会依据实例目标的isa指针指向类目标找到类目标,在类目标中找对应的办法。
其实,咱们也能够直接调用objc_msgSend
函数来调用对应办法。
知识小亮点
查看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
办法,打印成果是什么呢?
实践打印成果如下:[self class]
在编译时会转化为调用了objc_msgSend
函数,所以输出HGTeacher
,[super class]
在编译时转化为调用了objc_msgSendSuper
函数,而该函数和objc_msgSend
函数仅有的差异在于找办法的时分,前者是先从父类中找办法的完成
。同理,所以[super study]
打印出来的成果是先去父类中寻找该办法的完成,打印出-[HGPerson study]
。
为了更好的了解objc_msgSendSuper
函数的调用机制,咱们自己手动完成super
关键字,创立类型为objc_super
的结构体并为receiver
、super_ class
赋值,将objc_msgSendSuper
函数转化objc_msgSendSuperTyped
类型,然后找study
办法的完成。
留意⚠️:上述自己完成super关键字的办法为参阅网络办法。
当把super_class
变为HGTeacher.class
时,调用到objc_msgSendSuper
函数时又会来HGTeacher
类中找study
办法的完成,所以就会循环调用HGTeacher
类中的study
办法。
当把super_class
变为NSObject.class
时,调用到objc_msgSendSuper
函数时会去到NSObject
类中找study
办法的完成,但NSObject
类并没有该办法,所以会crash
。
总结:objc_msgSendSuper
函数和objc_msgSend
函数仅有的差异在于找办法的时分,前者是先从父类中找办法的完成
。
二.办法的快速查找流程
结合objc4-838.1
源码和汇编进行调试,跟踪study办法的查找流程。在[t study]处在增加断点,运转工程到此处后,在Debug-> Debug Workflow -> Always show Disassembly打开汇编调试工具,按住Control,点击Stepinto单步调试,进入objc msgSend
函数底层完成。
验证objc_msgSend
函数便是HGTeacher目标在调用study办法。
接下来正式进入查找流程。查看objc_msgSend
函数源码(arm64)发现这个函数的底层是经过汇编完成的(598行
),
知识小亮点
1.为什么objc_msgSend函数底层运用汇编?首要原因是汇编比c更块,一起汇编会免去大量对局部变量的复制操作 ,参数被直接存放在寄存器中。
2.汇编会在函数和全局变量前加一个下划线"_",在程序中往往会包括汇编和C文件,关于编译器来说两者是一样的,因而可能会出现问题,为了避免符号名的抵触在汇编中函数和全局变量前会加一个下划线"_"。如图:
第一步
,判断p0(音讯接收者)是否存在,不存在则重新开始履行objc_msgSend。经过读取寄存器证明此刻的x0的确是HGTeacher
目标。
第2步
,经过p13取到isa指针,经过打印实例目标t的内存地址验证的确是其isa指针地址。再经过isa取class并保存到p16寄存器中,读取p16寄存器得到的地址的确和HGTeacher类目标的地址相同。
第3步
,从x16中取出class移到x15中,经过x16内存平移得到cache
(x11)。
第4步
,经过cache
(x11)找存储办法的buckets
。经过iOS底层原理之cache底层详解中一、经过源码剖析cache的缓存内容
的办法调试查找。
第5步
,假如在cache中找到buckets,在buckets中找到对应的sel,就会调用cacheHit;假如没有找到,就会调用objc_msgSend_uncached函数。
总结:办法(实例办法和类办法)的快速查找
objc_msgSend(receiver,sel,<其他参数>(办法的参数))
(1)判断receiver
是否存在;
(2)经过receiver
的isa
指针找到对应的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行
)函数。
lookUpImpOrForward
函数完成中,如下部分是系统为调用此函数做的准备工作。
接着,再一次从cache
里边取找imp,这是误了避免多线程操作时刚好调用办法,此刻缓存进来了。假如在cache
还是找不到,就去办法列表中查找。
终究到类的办法列表中查找,查找办法为二分法查找!
经过遍历办法列表,运用二分法查找算法查找,成果可能为查找到或未查找到,如下图:
四.办法的慢速查找流程
三.办法的慢速查找算法
中假如从办法类表中查找到,履行done
,调用log_and_fill_cache
函数,这个函数中完成了将该办法插入到缓存中。
假如在办法列表中也没找到,就会将当时类赋为父类去父类中找,先到父类的缓存中找,找不到就会再次进入for (unsigned attempts = unreasonableClassCount() ;;)
循环到父类的办法列表中查找。
假如在父类的办法列表也没找到就到父类的父类查找,直到父类为nil,假如还没找到,就调用forward_imp
(办法转发
)。
总结:办法(实例办法和类办法)的慢速查找
lookUpImpOrForward
函数
(1)先在当时类的methodList
中查找,假如找到会进行缓存;
(2)在当时类的methodList
中没找到,去父类的cache
中查找;
(3)在父类的cache
中没找到就会到父类的methodList
中查找;
(4)直到父类为nil,假如还没找到,就会调用forward_imp
,即音讯的转发
。
五.关于办法查找的测验
创立一个新工程03_办法查找测验
,新建承继于NSObject
的HGPerson
类,没有任何办法,增加NSObject
的分类NSObject+Test
,在其间增加一个test
办法。在main.m
中运用HGPerson
调用test
办法,运转成果是怎样的呢?会崩溃吗?答案是不会崩溃,而是会正常调用test
办法正常打印成果。这其实便是验证上述办法查找的一个进程:先在当时类查找,当时类找不到就去父类,直到父类存在办法及其完成
!一起阐明NSObject是万类之主
!
遗留问题
当直到父类为nil时仍然没有找到该办法,就会进行音讯转发,这个音讯转发是个什么进程呢?
本文总结
1.先在当时类查找,当时类找不到就去父类,直到(NSObject的父类
)父类为nil,假如还没找到,就调用forward_imp
(办法转发
);
2.一切的办法查找进程都是为了查找到办法的完成,而不只是办法声明;
3.办法的查找进程中并没有对实例办法和类办法进行区分;
4.子类调用父类办法,其办法会缓存在子类中。
有任何问题,欢迎各位谈论指出!觉得博主写的还不错的麻烦点个赞喽