前世缘
上回说道OC方法的本质,其底层实现是一个objc_msgSend的东东。那么这个objc_msgSend本质又是个啥?它是一个C或C++函数吗??如果不是,它又是怎么实现的呢???
我们在看一下objc_msgSend的结构:
// 调用run方法,传入100参数
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("run"), 100);
他主要做了两件事:
- 向一个person类发送一个run消息
- 并附带一个100的参数
我们知道OC所有的方法都是调用objc_msgSend,那么C语言能实现吗?要实现objc_msgSend需要具备两个条件:
1.保存任意类型的未知参数
2.在任意位置可以跳到对应指针
显然C语言是做不到,那么runtime是怎么实现的???
今生孽
不入虎穴焉得虎子,讲到这里就必须去看看runtime源码了。我们去Apple的opensource下载runtime源码到本地,现在已经到818版本了,网上普遍流行的是750版本。在github也有一个解读runtime源码的project,大家有空可以去探究探究。
打开我们下载的runtime项目,全局搜索objc_msgSend:
发现木有,有个.s文件。这是一个什么文件呢?.s是汇编文件的后缀,那么我们继续往下查看,我们以arm64为例:
虽然我们并不太懂汇编,但是基本看命名和注释也能猜个大概。看这里有个ENTRY明显应该是入口的意思,它会先进行一个tagged pointer的判断,然后主要走入了CacheLookup NORMAL:
注意这里的注释,很明显是在找缓存。从buckets中里面取元素,如果找到的和_cmd匹配就命中缓存CacheHit;否则则未命中缓存CacheMiss。从CacheLookup入口的注释得知,他的另一条路径便是objc_msgSend_uncached:
MethodTableLookup:顾名思义,是方法列表查找的意思。
继续一步步深入,再查看下__class_lookupMethodAndLoadCache3,发现并未找到__class_lookupMethodAndLoadCache3,但是找到了_class_lookupMethodAndLoadCache:
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
哎,神奇了。我们终于从晦涩难懂的汇编跳到了还算熟悉的C++,之后便是C++的方法寻找函数了。
回首 & 思考
我们通过源码证实了objc_msgSend是前段部分是用汇编实现的,那么为什么要用汇编?
- 首先,是上面说道的C并不能满足条件。而汇编拥有寄存器,可以满足objc_msgSend实现的两个核心条件
- OC中所有的方法都是靠objc_msgSend实现的,使用汇编效率会相对更高
总结一下,这里objc_msgSend主要核心是两个步骤:
1.尝试从缓存里找方法,此时是使用了汇编实现
2.缓存未命中,继续使用C++方法寻找
故而,objc_msgSend的本质便是CacheLookup+lookUpImpOrForward。今天就探究到此,欲知后事如何,且听下回分解。。。
--20210901子时
我们必须像一座山,
既满生着芳草香花,
又有极坚硬的石头。
----------------------------- 老舍