ARC下,我们虽然不能再显式调用retain
和release
了(这些工作编译器自动优化了),但弄懂引用计数对于内存管理仍然非常有用。
一、retainCount
我们首先来看下retainCount
的实现,retainCount
用于返回对象的引用计数。
inline uintptr_t objc_object::rootRetainCount(){
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
if(bits.nonpointer){
uintptr_t rc = 1 + bits.extra_rc;
if(bits.has_sidetable_rc){
rc += sidetable_getExtraRC_nolock();
sidetable_unlock();
return rc;
}
}
sidetable_unlock();
return sidetable_retainCount();
}
这里我们只讨论nonpointer
成立的情况下,因为在64位机器下,绝大部分的对象都是nonpointer
。从函数中可以看出,引用计数rc = 1 + bits.extra_rc + sidetable_getExtraRC_nolock()
,由以下3个部分组成:
1. 1为对象创建之后的固定值。
2. bits.extra_rc为存储在结构体中的引用计数值。
3. sidetable_getExtraRC_nolock为存储在sideTable中的引用计数值。
接下来我们通过retain
来分析下为什么需要三个地方来存储一个值。
二、retain
首先来看下retain
函数的调用栈:
-[NSObject retain]
-rootRetain();
-rootRetain(false, false);
再看rootRetain
的简化后的实现逻辑:
#define RC_ONCE (1UL << 56)
#define RC_HALF(1UL << 7)
id objc_object::rootRetain(bool tryRetain, bool handleOverflow){
isa_t newisa = LoadExclusive(&isa.bits);
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
if(!carry){
return id(this);
}
newisa.extra_rc= RC_HALF;
sidetable_addExtraRC_nolock(RC_HALF);
return id(this);
}
在NSObject与isa中,我们介绍过bits.extra_rc
只有8位,能存储的位数有限(1<<7 - 1)。
- 首先通过
addc
对isa.extra_rc
进行+1操作,如果溢出的话carry
不为0,如果不溢出的话rootRetain
就完成了。 - 溢出之后,对
isa.extra_rc
赋值RC_HALF
,即能存储最大值的一半,另外一半则通过sidetable_addExtraRC_nolock
来存储。
sidetable_addExtraRC_nolock
的实现逻辑:
bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc){
SideTable& table = SideTables()[this);
oldRefcnt = &table.refcnts[this];
size_t newRefcnt = addc(oldRefcnt, delta_rc, 0, &carry);
return true;
}
sidetable_addExtraRC_nolock
通过SideTables
来存储extra_rc
存储不下的引用计数。
三、release
release
的调用栈:
-[NSObject release]
-rootRelease()
-rootRelease(true, false);
rootRelease
的实现:
bool objc_object:rootRelease(bool perfromDealloc, bool handleUnderflow){
isa_t newisa = LoadExclusive(&isa.bits);
newisa.t = subc(newisa.bits, RC_ONE, 0, &carry);
if(!carry){
return false;
}
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if(borrowed){
newisa.extra_rc = borrowed - 1;
return false;
}
newisa.deallocating = true;
if(peformDealloc){
((void(*)(objc_object *, SEL))objc_msgSend(this, SEL_dealloc);
}
return true;
}
- 首先用
addc
对extra_rc
进行-1操作,如果不溢出则直接返回。 - 如果步骤1溢出了,则通过
sidetable_subExtraRC_nolock
借RC_HALF
对应的引用计数值,如果不为0,则对extra_rc = borrowed - 1
赋值。 - 如果步骤2中
borrowed
为0,则引用计数为0,标记为deallocating = true
,并同时调用SEL_dealloc
方法。
小结:
- ARC下
retain
和release
中方法无法显式调用,编译器已经自动帮我们优化好了。 - 引用计数存在三个地方,固定的1、
isa.extra_rc
和sideTable
中。sideTable
用以赋值extra_rc
溢出时的引用计数值。