OC底层-objc_msgSend

·  阅读 652
OC底层-objc_msgSend

前面OC底层-runtime中我们了解到了方法调用的本质就是消息发送,那么这篇我们探索objc_msgSend的底层原理,看看调用方法后系统的消息发送机制。

objc_msgSend

1、在objc源码中进行搜索objc_msgSend(然后我们会发现比较多的内容,搜索结果如下:

image.png

2、因为我们要看函数的实现,所以直接排除.h文件,然后看剩下的文件中有一个dummy-libary-mac-i385.c打开一看是objc_msgSend的定义,所以也排除,然后就看剩下的几个文件都是.s结尾的,一般来说.s的文件都是汇编语言代码,而且我们目前是在虚拟机跑的项目,所以我们直接选择架构是i386.s文件进行查看分析。

3、我们能够看到整个objc_msgSend汇编代码如下所示:

image.png

4、上面的汇编代码大概流程如下图所示:

未命名.png

5、以上就是我们在objc源码中所能看到的objc_msgSend的一个流程,苹果在这块直接使用汇编代码的原因应该是出于效率考虑,因为汇编是最接近机器语言的一种编码了。

cache补充

1、介绍完objc_msgSend后,我们继续介绍cache的扩展内容,前面我们了解了cache的一个流程,但是我们并没有去详细分析cache的存储过程,接下来我们去看下cache是怎么将方法进行缓存的。

2、今天我们就从void cache_t::insert(SEL sel, IMP imp, id receiver)函数开始探索,方法缓存的过程。首先我们看下这个函数的实现:

image.png

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)函数的实现:

image.png

7、进入这个函数我们能够看到,先获取buckets()指针,然后根据新值去开辟内存,设置cache成员数据。然后我们看下setBucketsAndMask函数的实现:

image.png

8、因为我们项目运行在arm64架构下,所以我们直接看arm架构的逻辑。然后我们回到insert函数,看下其他的条件分支判断,这里有个比较重要的数据就是3/47/8这是一个系数,我们已经知道缓存是一个哈希表,在这个系数下,哈希表的重复误差能够降到一个接受范围内,降低了重复值的可能性。然后另外一个就是如果缓存满了,那么就重新走reallocate函数,开辟新的内存,释放旧的内存。

9、在第873行有个重点mask_t m = capacity - 1;这块是查找存储空间,但是是往前查找的,然后进行一个遍历,查找能够进行插入的空白内存,找到后将方法缓存到桶子中。

10、到这里cache缓存的基本流程探索已经完成,后续有新的发现,我们再补充。

分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改