前言

早在MRC时代,引用计数是由我们开发者自己控制的,但是到了ARC时代,编译器帮我们做了引用计数管理这一步。这边的描述,就是为了解密,引用计数是如何实现的。如果不知道什么是引用计数,请参考其他人的文章~

前提

本文的所有内容均已下述标准下进行的实验

ISA

在了解引用计数前,我们来回顾一下ISA的isa_t的结构体中部分内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 由于我们的实验是基于x86_64的这边就列举了x86_64的结构体
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};

其中,我们可以看到的是有两个字段,我们先来做一下简单的了解extra_rchas_sidetable_rc,这两个变量就是用来存储引用计数的关键内容。
extra_rc : 用8位来存储引用计数大小 ->256
has_sidetable_rc : 如果extra_rc不够存储了,那么就会标记has_sidetable_rc为1,并且存储到另外一个Table表中

retain

描述

用于引用计数加1的操作的函数
retain函数内实际是调用rootRetain函数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// carry Flag
uintptr_t carry;
// 先存储在extra_rc字段中
// carry标识超过了256之后会存储在其他地方 并且extra_rc减半 返回carry 为1
// 这个代码没找到源码,网址是别人的研究成果
// https://www.jianshu.com/p/18c3e88dfbf1
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
// carry为1的时候,重新进入rootRetain函数,进入SideTable的加入
return rootRetain_overflow(tryRetain);
}
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
// 存储新的bits进入isa
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 存储一半的容量到SideTable中,x86下即为128
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
NEVER_INLINE id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}

代码

有点长~简化成流程图,放到这边了,代码就不贴了。

流程图

retain.png

流程图为了方便区分,分了两条线。

总结

  • 总的来说extra_rchas_sidetable_rc加上SideTable来控制引用计数的
  • extra_rc存储量比较小的计数标识,溢出则移出一部分交由另外两个值
  • has_sidetable_rc只是标识是否有SideTable的存在,具体溢出的值还是存储在SideTable中的
  • SideTable,用来存储溢出的引用计数的Map的,当然他还有其他作用,这边就只说明这个

系统是允许重写retain/release这些函数的,并且在调用真正的rootRetain之前会判断有没有实现自定义的retain函数
SideTable的具体实现部分没有具体源码就不深究了。每个对象都有自己的SideTable是独立的,只要了解至此我觉得已经够了

release

描述

用于引用计数减1的函数,当引用计数真正为0的时候,则调用真正的Release

流程图

release.png

总结

  • 引用计数先从extra_rc中进行减1操作
  • 如果extra_rc为0,那么检查SideTable,如果SideTable中有数值,则取出extra_rc容量一半的数值,存入extra_rc,再做减法
  • extra_rcSideTable的引用计数都为0的时候,利用msgSend函数进行真正的dealloc函数的调用

引用计数总结

引用计数的本质就是一个属性的加减以及一个Table的存储,并没有什么比较高深的东西。只当了解即可~