前面一篇文章,我们讲到了系统为了优化数字字符串等类型的数据存储新增了一种NSTaggedPointer
类型,同时我们还发现,isa
指针在经过优化后,提供了19个bit位用来存储引用计数的个数。但是如果超出了这个限制呢?
引用计数
其实在绝大多数情况下,仅用优化的isa_t来记录对象的引用计数就足够了,但是当对象被引用次数超过 2^19 限制时,就轮到SideTable出场了。
首先,我们先看下超出限制之后,系统是如何将引用计数转移的
retian
// 等价于直接使用对象调用retain方法
inline id
objc_object::retain()
{
// 如果是TaggedPointer类型 不涉及引用计数
ASSERT(!isTaggedPointer());
// fastpath 表示if中的条件是一个大概率事件
// 如果当前对象没有自定义(override)retain 方法
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
// 如果有自定义的retain方法
// 通过发消息的方式调用自定义的 retain 方法
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
接下来我们主要看下 引用计数+1的主要负责函数rootRetain
rootRetain
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
// 如果如果是taggedPointer直接返回不需要引用计数
if (isTaggedPointer()) return (id)this;
// 默认不使用sideTable
bool sideTableLocked = false;
// 是否需要将引用计数转到sidetable
bool transcribeToSideTable = false;
// 记录新旧两个isa指针
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
通过 LoadExclusive 方法加载 isa 的值,加锁
oldisa = LoadExclusive(&isa.bits);
// 此时 newisa = oldisa
newisa = oldisa;
// slowpath表示if中的条件是小概率事件
// 如果newisa(此时和oldisa相等) 如果没有采用isa优化
if (slowpath(!newisa.nonpointer)) {
// 解锁
ClearExclusive(&isa.bits);
//rawISA() = (Class)isa.bits
// 如果当前对象的 isa 指向的类对象是元类(也就是说当前对象不是实例对象,而是类对象),直接返回
if (rawISA()->isMetaClass()) return (id)this;
// 如果不需要retain对象(引用计数+1) 且sideTable是锁上的
if (!tryRetain && sideTableLocked)
// sidetable解锁
sidetable_unlock();
if (tryRetain)
// sidetable_tryRetain 尝试对引用计数器进行+1的操作 返回+1操作是否成功
return sidetable_tryRetain() ? (id)this : nil;
else
// 将sidetable中保存的引用计数+1同时返回引用计数
return sidetable_retain();
}
// 如果需要尝试 +1 但是当前对象正在销毁中
if (slowpath(tryRetain && newisa.deallocating)) {
// 解锁
ClearExclusive(&isa.bits);
// 如果不需要去尝试 +1 并且 SideTables 表锁住了,就将其解锁
// 这里的条件 应该永远都不会被满足
if (!tryRetain && sideTableLocked)
sidetable_unlock();
// 如果对象正在被释放 执行retain是无效的
return nil;
}
// 引用计数是否溢出标志位
uintptr_t carry;
//为 isa 中的 extra_rc 位 +1 ,并保存引用计数
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// 如果 isa中的extra_rc 溢出
if (slowpath(carry)) {
// newisa.extra_rc++ 溢出
// 是否需要处理溢出 这个变量是rootRetain函数外部传入的参数 是否需要处理溢出时的情况
if (!handleOverflow) {
//解锁
ClearExclusive(&isa.bits);
// rootRetain_overflow 方法实际上就是递归调用了当前方法只是将handleOverflow
// 置为yes
return rootRetain_overflow(tryRetain);
}
// 保留isa中extra_rc一半的值 将另一半转移到sidetable中
// 如果不需要尝试 +1 并且 sidetable 表未加锁,就将其加锁
if (!tryRetain && !sideTableLocked) sidetable_lock();
// sidetable加锁
sideTableLocked = true;
// 需要将引用计数转移到sidetable
transcribeToSideTable = true;
// 将newisa中的引用计数置为之前的一半 # define RC_HALF (1ULL<<18)
newisa.extra_rc = RC_HALF;
// isa中是否使用sidetable存储retiancount的标志位置为1
newisa.has_sidetable_rc = true;
}
//while循环开始 直到 isa.bits 中的值被成功更新成 newisa.bits
// StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value)
// 将更新后的newisa的值更新到isabit中
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
// 如果需要转移引用计数到sidetable中
if (slowpath(transcribeToSideTable)) {
// 将溢出的引用计数加到 sidetable 中
sidetable_addExtraRC_nolock(RC_HALF);
}
// 如果不需要去尝试 +1 并且 SideTables 表锁住了,就将其解锁
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
// 返回当前对象 引用计数已完成+1操作
return (id)this;
}
当tryRetain
标志位为1时,我们会先尝试调用sidetable_tryRetain
方法,我们先看下这个方法:
sidetable_tryRetain
//尝试将 SideTable 表中的引用计数 +1
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
// 根据对象地址获取到引用计数器所在的sideTable
SideTable& table = SideTables()[this];
// NO SPINLOCK HERE
// _objc_rootTryRetain() is called exclusively by _objc_loadWeak(),
// which already acquired the lock on our behalf.
// fixme can't do this efficiently with os_lock_handoff_s
// if (table.slock == 0) {
// _objc_fatal("Do not call -_tryRetain.");
// }
bool result = true;
// try_emplace 如果给定的key在容器中不存在,原位构造一个元素 如果在容器中则返回
// try_emplace 有两个返回值 第一个返回值是一个遍历器 第二个返回值表示 key value 在map中是否已存在
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
// 获取引用计数(含两个标志位)
auto &refcnt = it.first->second;
if (it.second) {
//如果这个对象对应的实体第一次插入到sidetable中
} else if (refcnt & SIDE_TABLE_DEALLOCATING) {
// 如果当前对象处于正在被销毁状态SIDE_TABLE_DEALLOCATING标志位为1
result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
// 引用计数没有溢出 + 1
refcnt += SIDE_TABLE_RC_ONE;
}
// 返回值为引用计数是否成功+1
return result;
}
当前的对象没有处于正在被销毁的状态时,我们会将sidetable中的引用计数+1。
如果tryRetain
标志位为0,那么我们直接调用sidetable_retain
方法对引用计数器进行+1操作,sidetable_retain
方法如下:
sidetable_retain
// 将 SideTable 表中的引用计数 +1
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
// 根据对象获取 存储引用计数的sidetable
SideTable& table = SideTables()[this];
table.lock();
// 获取sidetable中存储的引用计数值
size_t& refcntStorage = table.refcnts[this];
// 如果引用计数值没有溢出
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
// 引用计数值+SIDE_TABLE_RC_ONE
// #define SIDE_TABLE_RC_ONE (1UL<<2)
// SIDE_TABLE_RC_ONE = 4 为什么这里会+4我们下面会介绍
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
通过上面这个方法,我们对引用计数器完成了+1(实际上是+4)的操作,那么这里为什么会+4呢?
那是因为对于table.refcnts
,实际上并不完全是表示引用计数的值,refcnts的最后两位有特殊的标示意义:
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1)
- 倒数第一位标记当前对象是否被weak指针指向(1:有weak指针指向);
- 倒数第二位标记当前对象是否正在销毁状态(1:处在正在销毁状态);
因此,我们每次执行retain方法时,虽然每次都是+4,但是对于引用计数真实的值来说就是+1,64位环境下只有62位是保存溢出的引用计数的.
紧接如果对象没有采用isa优化且对象没有正在销毁,我们通过调用addc
方法实现引用计数器+1的操作,这个方法会给我们一个标志值carry
,表示进行+1操作后,引用计数是否溢出。
如果发生溢出,但是此时我们不需要处理溢出:
那么我们会直接调用rootRetain_overflow
方法,我们先来看下这个方法:
rootRetain_overflow
NEVER_INLINE id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}
很明显 这个方法实际是递归调用了rootRetain
方法,只是handleOverflow
参数值被置为yes
。而对于retain操作来说实际上是走出了刚才if (!handleOverflow)
判断。那么我们继续往下看。
如果发生溢出,且我么需要处理溢出时:
我们需要先设置标志位:
- sideTableLocked = true;
- transcribeToSideTable = true;
- newisa.extra_rc = RC_HALF;
- newisa.has_sidetable_rc = true;
同时将newisa
的值更新到isa
中,保存成功后,while循环结束。
紧接着我们调用sidetable_addExtraRC_nolock
方法,下面我们再来看下这个方法:
if (slowpath(transcribeToSideTable)) {
//拷贝 平外一半的 引用计数到 side table
sidetable_addExtraRC_nolock(RC_HALF);
}
RC_HALF
的定义如下
#define RC_HALF (1ULL<<18)
我们都知道NSTaggedPointer
预留了19个bit位用来存放引用计数,RC_HALF
的值刚好为 2^19 次方的一半。
我们下面来看下sidetable_addExtraRC_nolock
如何实现的
sidetable_addExtraRC_nolock
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
// 将isa中的引用计数移动到sidetable中 当引用计数达到最大值(溢出)是返回true
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
// 根据对象地址获取到存放引用计数对应的sidetable
SideTable& table = SideTables()[this];
// 从table.refcnts中获取当前对象的引用计数
size_t& refcntStorage = table.refcnts[this];
// 声明一个局部变量存储旧的引用计数
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
// 如果就的引用计数>0或sidetable正在销毁
// 如果引用计数>0或当前对象再被其他对象弱引用
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
// #define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
// oldRefcnt & SIDE_TABLE_RC_PINNED = 1 就是 oldRefcnt = 2147483648 (32位情况)
// 这时候 引用计数已经超过了三十二位所能表达的最大值 直接返回true
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
// 溢出标志位
uintptr_t carry;
// 对oldRefcnt执行+delta_rc操作
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
// 如果引用计数溢出 设置标识为已满
if (carry) {
// 如果是32位的情况 SIDE_TABLE_RC_PINNED = 1<< (32-1)
// int的最大值 SIDE_TABLE_RC_PINNED = 2147483648
// SIDE_TABLE_FLAG_MASK = 3
// refcntStorage = 2147483648 | (oldRefcnt & 3)
// 如果溢出,直接把refcntStorage 设置成最大值
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
// 如果没有溢出 那么直接将新的引用计数赋值给refcntStorage
refcntStorage = newRefcnt;
return false;
}
}
向右偏移两位的原因是,RefcountMap refcnts的最后两位有特殊的标示意义:
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1)
- 倒数第一位标记当前对象是否被weak指针指向(1:有weak指针指向);
- 倒数第二位标记当前对象是否正在销毁状态(1:处在正在销毁状态);
所以,64位环境下只有62位是保存溢出的引用计数的.
通过上面的介绍我们了解到了引用计数是如何在sidetable中存储的(retian方法)。那么引用计数-1的操作又是怎么实现的呢?
release
// Equivalent to calling [this release], with shortcuts if there is no override
// 等价于直接使用对象调用release方法
inline void
objc_object::release()
{
ASSERT(!isTaggedPointer());
// 如果没有自定义的release方法 就直接调用rootRelease
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
// 如果有自定义的release方法那么调用对象的release方法
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}
从上面的代码我们看到,在没有自定义release方法时,系统默认是调用的rootRelease
方法,下面我们来看下这个方法。
rootRelease
ALWAYS_INLINE bool
objc_object::rootRelease()
{
// 调用了私有函数 rootRelease 去实现
return rootRelease(true, false);
}
上面这个方法实际上调用了同名函数(两个默认参数),下面我们进一步看下带有两个参数的rootRelease
方法:
// 真正的release方法
// 两个参数分别是 是否需要调用dealloc函数,是否需要处理 向下溢出的问题
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// 如果是TaggedPointer 不需要进行release操作
if (isTaggedPointer()) return false;
// 局部变量sideTable是否上锁 默认false
bool sideTableLocked = false;
// 两个局部变量用来记录这个对象的isa指针
isa_t oldisa;
isa_t newisa;
retry:
do {
// 加载这个isa指针
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// 如果没有进行nonpointer优化
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
// 如果是类对象直接返回false 不需要释放
if (rawISA()->isMetaClass()) return false;
// 如果sideTableLocked 则解锁 这里默认是false
if (sideTableLocked)
sidetable_unlock();
// 调用sidetable_release 进行引用计数-1操作
return sidetable_release(performDealloc);
}
// 溢出标记位
uintptr_t carry;
// newisa 对象的extra_rc 进行-1操作
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果-1操作后 向下溢出了 结果为负数
if (slowpath(carry)) {
// don't ClearExclusive()
// 调用underflow 进行向下溢出的处理
goto underflow;
}
// 开启循环,直到 isa.bits 中的值被成功更新成 newisa.bits
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
//走到这说明引用计数的 -1 操作已完成
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
//newisa的extra_rc在执行-1操作后导致了向下溢出
// 放弃对newisa的修改 使用之前的oldisa
newisa = oldisa;
// 如果 isa 的 has_sidetable_rc 标志位标识引用计数已溢出
// has_sidetable_rc 用于标识是否当前的引用计数过大,无法在isa中存储,
// 而需要借用sidetable来存储。(这种情况大多不会发生)
if (slowpath(newisa.has_sidetable_rc)) {
// 是否需要处理下溢
if (!handleUnderflow) {
// 清除原 isa 中的数据的原子独占
ClearExclusive(&isa.bits);
// 如果不需要处理下溢 直接调用 rootRelease_underflow方法
return rootRelease_underflow(performDealloc);
}
// 如果sidetable是上锁状态
if (!sideTableLocked) {
// 解除清除原 isa 中的数据的原子独占
ClearExclusive(&isa.bits);
// sidetable 上锁
sidetable_lock();
sideTableLocked = true;
// 跳转到 retry 重新开始,避免 isa 从 nonpointer 类型转换成原始类型导致的问题
goto retry;
}
// sidetable_subExtraRC_nolock 放回要从sidetable移动到isa的extra_rc的值
// 默认是获取extra_rc可存储的长度一半的值
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
// 为了避免冲突 has_sidetable_rc 标志位必须保留1的状态 及时sidetable中的个数为0
if (borrowed > 0) {
// 将newisa中引用计数值extra_rc 设置为borrowed - 1
// -1 是因为 本身这次是release操作
newisa.extra_rc = borrowed - 1;
// 然后将修改同步到isa中
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
// 如果保存失败
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
// 从新装载isa
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
// 如果newisa2是nonpointer类型
if (newisa2.nonpointer) {
// 下溢出标志位
uintptr_t overflow;
// 将从 SideTables 表中获取的引用计数保存到 newisa2 的 extra_rc 标志位中
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
//
if (!overflow) {
// 如果没有溢出再次将 isa.bits 中的值更新为 newisa2.bits
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
// 如果重试之后依然失败
if (!stored) {
// 将从sidetable中取出的引用计数borrowed 重新加到sidetable中
sidetable_addExtraRC_nolock(borrowed);
// 重新尝试
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
// 完成对 SideTables 表中数据的操作后,为其解锁
sidetable_unlock();
return false;
}
else {
// 在从Side table拿出一部分引用计数之后 Side table为空
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 如果当前的对象正在被释放
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
// 如果sideTableLocked被锁 那么解锁
if (sideTableLocked) sidetable_unlock();
// 兑现被过度释放
return overrelease_error();
// does not actually return
}
// 将对象被释放的标志位置为true
newisa.deallocating = true;
// 将newisa同步到isa中 如果失败 进行重试
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))
goto retry;
// 如果sideTableLocked= true
if (slowpath(sideTableLocked))
// Side table解锁
sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
// 如果需要执行dealloc方法 那么调用该对象的dealloc方法
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
这个方法还是有点长的,我们看到主要是由两个内部方法retry
,underflow
组成,下面我们来一步步的整理下引用计数-1操作的具体步骤
retry
sidetable_release
如果这个对象没有nonpointer
优化,且不是一个类对象,那么我们直接通过对sidetable进行-1操作
// 将 SideTable 表中的引用计数 -1
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
// 根据对象地址获取SideTable
SideTable& table = SideTables()[this];
// 是否需要执行dealloc方法 默认是false
bool do_dealloc = false;
table.lock();
// 获取当前对象的销毁状态 方法的返回值有2个
// 引用计数和当前对象是否已存在与map中
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
// 如果当前对象之前不存在与map中
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// 如果引用计数的值小于 SIDE_TABLE_DEALLOCATING = 2(0010)
// refcnt 低两位分别是SIDE_TABLE_WEAKLY_REFERENCED 0 SIDE_TABLE_DEALLOCATING 1
// 这个对象需要被销毁
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
// 如果引用计数有值且未溢出那么-1
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
// 如果需要执行dealloc 那么就调用这个对象的dealloc
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
相反,如果该对象做了nonpointer优化,那么我们直接对extra_rc进行-1操作,即
// newisa 对象的extra_rc 进行-1操作
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果-1操作后 向下溢出了 结果为负数
if (slowpath(carry)) {
// don't ClearExclusive()
// 调用underflow 进行向下溢出的处理
goto underflow;
}
将extra_rc计数-1,如果发现-1操作之后,extra_rc的个数为0,那么就出现了向下溢出,我们需要将sideTable中的部分引用计数拿到extra_rc中记录。如果没有向下溢出,那么我们就直接将修改后的newisa同步到isa中即完成了release操作。
underflow
如果在将extra_rc进行-1操作时,出现了向下溢出的问题,那么我们需要将sideTable中的引用计数移动到extra_rc中存储。
下面我们来分析下具体过程
先判断has_sidetable_rc
是否有sidetable引用计数,如果有我们要确认是否需要处理向下溢出,如果不需要处理向下溢出,那么我们直接调用rootRelease_underflow
方法,
rootRelease_underflow
NEVER_INLINE uintptr_t
objc_object::rootRelease_underflow(bool performDealloc)
{
return rootRelease(performDealloc, true);
}
很明显这个方法实际上与retain操作时处理溢出逻辑相同,将rootRelease
方法中的handleUnderflow
参数置为true,要处理向下溢出。
下面我们再来看下,需要处理向下溢出时,如果当前的sidetable处于未上锁的状态时,将sidetable上锁然后进行重试,如果sidetable未已经上锁了,那么我们会执行下面这句代码:
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
sidetable_subExtraRC_nolock 返回要从sidetable移动到isa的extra_rc的值,默认是获取extra_rc可存储的长度一半的值。
如果此时从sidetable中拿到的值 > 0,那么我们要将这部分值放到isa的extra_rc中进行存储,如果取到的borrowed的值为0,那么说明sidetable中的引用计数为0,那么我们直接释放该对象即可。
StoreReleaseExclusive
上面说到如果从sidetable中获取到的值borrowed大于0,那么我们直接将newisa.extra_rc
设置为borrowed - 1
即可。
然后我们在调用StoreReleaseExclusive
方法将newisa
同步到isa
中。
如果这里StoreReleaseExclusive
方法保存失败了,那么我们需要重新调用LoadExclusive
重新声明两个变量newisa2
,oldisa2
。通过addc
方法将extra_rc
置为borrowed-1
newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
然后再次调用StoreReleaseExclusive
方法将newisa2
的改动同步到isa
中。
如果StoreReleaseExclusive
方法依然保存失败,那么我们就把从sidetable中获取的borrowed
重新加到sideTable中。然后调用retry方法。
经过StoreReleaseExclusive
这一步,引用计数更新操作完成。但是如果此时的引用计数为0我们改如何操作呢?
如果引用计数更新成功,那么我们需要先判断,当前对象是否正在被释放,如果正在被释放 那么调用过度释放方法overrelease_error
overrelease_error
NEVER_INLINE uintptr_t
objc_object::overrelease_error()
{
_objc_inform_now_and_on_crash("%s object %p overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug", object_getClassName((id)this), this);
objc_overrelease_during_dealloc_error();
return 0; // allow rootRelease() to tail-call this
}
这个方法主要是定义了crash信息,当一个用户正在被释放时,再次调用release方法时会导致crash,具体crash信息如上述代码。
如果当前对象没有被正在释放,那么我们将当前对象正在被释放标志位置为true newisa.deallocating = true;
同时将状态的更新同步到isa
中。如果同步失败,那么会重复走一次retry。
更新状态成功后,对sidetable的操作也结束了,我们就可以将sidetable解锁(sidetable_unlock),如果需要执行dealloc方法,那么我们调用dealloc方法进行对象释放通知。
总结
至此我们就看完了objc对于retain和release以及其对引用计数的操作,以及在retain操作时当extra_rc空间不足时,引用计数是如何从extra_rc转移到sidetable中和release操作时引用计数是如何从sidetable转移到extra_rc中的。希望看了这篇文章可以帮你更好的了解引用计数的实现。