retain和release实现探究

本文深入探讨了Objective-C中对象引用计数的管理机制,包括使用NSTaggedPointer优化、isa指针的引用计数存储以及当超出19位限制时如何利用SideTable进行扩展。详细介绍了retain、release方法的实现过程,包括如何处理引用计数溢出和下溢的情况,以及在sidetable中存储和转移引用计数的方法。
摘要由CSDN通过智能技术生成

前面一篇文章,我们讲到了系统为了优化数字字符串等类型的数据存储新增了一种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中的。希望看了这篇文章可以帮你更好的了解引用计数的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值