携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天, 点击查看活动详情
引言
上一篇中说到实例方法缓存在类的cache_t中,是通过提供的inset(sel, imp, cls)讲方法缓存起来,我们直接拦腰截断从插入方法开起来的,但是我们并不知道是谁引起的insert()也就没有办法形成闭环,下面要探索的就是何时出发的insert()。
首先在源码中insert打下断点,查看堆栈信息
_objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> insert,根据这个流程,可以这样理解么?"首次调用时方法没有找到,开始寻找方法,最终把方法插入到缓存中",这里是个问句,需要探索
调用方法底层实现
main.m文件的实现如下:
这里想知道
[person saySomething]消息发送在底层做了什么,使用clang命令转换成c++,在main.m所在文件夹执行以下命令:
clang -rewrite-objc main.m -o main.cpp
执行完成后,会看到一个和main.m同级的main.cpp文件,打开并找到DXJPerson的实现
上图中可以发现:
[person saySomething]在OC层面方法实现是没有带参数的,其实在底层是现实过程中带了两个隐藏参数DXJPerson * self, SEL _cmd[person saySomething]在底层是这样发送消息的
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
简化后:
objc_msgSend(person, sel_registerName("saySomething"));
objc_msgSend()是消息发送的底层实现,那是不是我们也可以使用该api发送消息呢?答案是可以的,看下图
在objc_msgSend()过程中,发现了另一个有意思的函数objc_msgSendSuper(),他是这样定义的,内部也有两个隐藏参数,一个是常见的sel,另一个是objc_super *类型的结构体指针
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
打开objclib.A.dylib源码库,看下objc_super结构体内部实现有两个属性:
receiver: 消息的接受者super_class: the first class to search(首先从哪个类开始被检索方法)
如下图:
根据objc_msgSendSuper()的定义,使用该函数去发送一个消息:
objc_msgSend之汇编流程
到这里已经知道了消息的发送是通过objc_msgSend()实现,要想知道底层是实现,首先通过汇编跟流程,在[person saySomething]打下断点,然后选择Debug->Debug workflow ->Always show Disassembly(这里有打断点的几种方式)
objc_msgSend是属于libobjc.A.dylib库,打开该源码库,全局搜索objc_msgSend方法,因为该函数的底层是用汇编实现的所以只用找.s文件即可
下面分析一下汇编中
objc_msgSend的实现
cmp p0, #0, 根据源码提供的解释,p0是接受者的isa,拿p0和#0比较,是检测接受者是否是nil或者tagged pointer类型ldr p13, [x0],x0receive的isa地址,将[x0]地址移入到p13中GetClassFromIsa_p16 p13, 1, x0, 下图是GetClassFromIsa_p16的实现,红框中表示的是不同架构类型下的操作,想要做的事情是一样的,所以这里只拿一种做分析else流程ExtractISA p16, \src, \auth_address,src就是传递过来isa,根据下方源码的实现,将isa & ISA_MASK赋值给p16,至此类的地址获取到了,放在p16中.macro ExtractISA and $0, $1, #ISA_MASK // 将($1 & ISA_MASK)然后赋值给$0,p16 = $0 , \src = $1, \auth_address = $2CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached至此开始CacheLookup流程