前面OC底层-runtime中我们了解到了方法调用的本质就是消息发送,那么这篇我们探索objc_msgSend
的底层原理,看看调用方法后系统的消息发送机制。
objc_msgSend
1、在objc
源码中进行搜索objc_msgSend(
然后我们会发现比较多的内容,搜索结果如下:
2、因为我们要看函数的实现,所以直接排除.h
文件,然后看剩下的文件中有一个dummy-libary-mac-i385.c
打开一看是objc_msgSend
的定义,所以也排除,然后就看剩下的几个文件都是.s
结尾的,一般来说.s
的文件都是汇编语言代码,而且我们目前是在虚拟机跑的项目,所以我们直接选择架构是i386
的.s
文件进行查看分析。
3、我们能够看到整个objc_msgSend
的汇编代码
如下所示:
4、上面的汇编代码大概流程如下图所示:
5、以上就是我们在objc
源码中所能看到的objc_msgSend
的一个流程,苹果在这块直接使用汇编代码
的原因应该是出于效率考虑,因为汇编
是最接近机器语言的一种编码了。
cache补充
1、介绍完objc_msgSend
后,我们继续介绍cache
的扩展内容,前面我们了解了cache
的一个流程,但是我们并没有去详细分析cache
的存储过程,接下来我们去看下cache
是怎么将方法进行缓存的。
2、今天我们就从void cache_t::insert(SEL sel, IMP imp, id receiver)
函数开始探索,方法缓存的过程。首先我们看下这个函数的实现:
3、首先进入函数后先进行了加锁操作,保证读写操作的安全性,然后进行了一个判断,判断当前消息发送者是否进行initialize
,如果该方法还未完成,直接返回,不进行缓存操作,然后就是进行了一个判断,调用了isConstantOptimizedCache()
函数,我们看下这个函数的实现inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
直接是返回了false
所以跳过这个继续往下看,重点在于850
行开始,机械能的一系列处理。
4、首先获取cache
中的_occupied
成员(保存buckets
中真实存储了多少方法的个数),然后进行+1
操作,得到一个新值,然后调用了一个函数capacity()
,我们先看下这个函数的实现:
unsigned cache_t::capacity() const
{
return mask() ? mask()+1 : 0;
}
复制代码
5、调用了mask()
,这个mask()
主要就是经过一系列的处理读取容量然后-1
得到一个值,然后判断得到的这个值是否大于0
,如果大于0
进行+
操作,如果不是直接返回0
。
6、然后去判断是否是空缓存,如果是空缓存,那么先进行内存开辟,调用reallocate
函数,在调用函数前先进行了一次判断capacity
是否为0
,如果是0
那么直接赋值为4
,然后看下void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
函数的实现:
7、进入这个函数我们能够看到,先获取buckets()
指针,然后根据新值去开辟内存,设置cache
成员数据。然后我们看下setBucketsAndMask
函数的实现:
8、因为我们项目运行在arm64
架构下,所以我们直接看arm
架构的逻辑。然后我们回到insert
函数,看下其他的条件分支判断,这里有个比较重要的数据就是3/4
和7/8
这是一个系数,我们已经知道缓存是一个哈希表,在这个系数下,哈希表的重复误差能够降到一个接受范围内,降低了重复值的可能性。然后另外一个就是如果缓存满了,那么就重新走reallocate
函数,开辟新的内存,释放旧的内存。
9、在第873
行有个重点mask_t m = capacity - 1;
这块是查找存储空间,但是是往前查找的,然后进行一个遍历,查找能够进行插入的空白内存,找到后将方法缓存到桶子中。
10、到这里cache
缓存的基本流程探索已经完成,后续有新的发现,我们再补充。