NSObject 底层原理分析(二) -- SideTable

上一篇NSObject 底层原理分析(一)中我们讨论了关于[[NSObject alloc] init],[NSObject new]以及[NSObject dealloc]的执行流程。其中,我们有提到释放NSObject中会判断当前的isa是否经过SideTable优化。因此,这篇文章,我们将研究SideTable到底是什么?!

SlideTable 结构

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

SideTable结构体构成:

  1. spinlock_t slock 自旋锁slock;
  2. RefcountMap refcnts 强引用相关;
  3. weak_table_t weak_table 弱引用相关;

Side Table初始化与析构函数

SideTable() {
  memset(&weak_table, 0, sizeof(weak_table));
}

~SideTable() {
    _objc_fatal("Do not delete SideTable.");
}

Side Table 提供方法

void lock() { slock.lock(); } // 调用自旋锁方法
void unlock() { slock.unlock(); } // 解除自旋锁方法
void forceReset() { slock.forceReset(); } // 锁重制方法
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);

自旋锁原理
它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

SlideTable 调用

SlideTable 引用计数+1

当对象调用[obj retain]时候,实际上会出发Objc4中objc_object::rootRetain调用,表明当前引用计数+1,我们来看一下在这个方法中到底做了什么操作。

if (isTaggedPointer()) return (id)this;

首先,判断当前obj对象是否是Tagged Pointer标记过的对象(上一篇我们知道苹果针对64bit设备提供了直接存储小对象的Tagged Pointer技术),当前对象被Tagged Pointer Mask过后则直接放回当前对象即可。

bool sideTableLocked = false;
bool transcribeToSideTable = false;

isa_t oldisa;
isa_t newisa;

do{
} while(slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

没有被Tagged Pointer标记过的对象则会继续执行上面这些代码,初始化Side Table部分信息。StoreExclusive()函数封装了&isa.bits 和 oldisa.bits 进行原子比较字节逐位相等的话,则把 newisa.bits 复制这一部分逻辑。

return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED);

do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

do-while循环中执行的LoadExclusive(&isa.bits)读取isa的bits信息,并赋值给了oldisa。判断当前newisa没有被Tagged Pointer标记过,则执行以下代码。

ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
  1. 当前对象是元对象则直接返回obj;
  2. 当前对象没有tryRetain引用并且被sidetable的自旋锁锁住则执行自旋锁解锁操作;
  3. 当前对象tryRetain则sidetable进行引用;

这里我们重点关注Side Table Retain做的操作:

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
  1. Slide Table强引用当前obj对象,执行引用计数+1操作refcntStorage += SIDE_TABLE_RC_ONE;
if (slowpath(transcribeToSideTable)) {
    // Copy the other half of the retain counts to the side table.
      sidetable_addExtraRC_nolock(RC_HALF);
  }
  
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}
  1. 根据this,从Side Tables中取出Side Table;
  2. 获取 SideTable 的 refcnts,这个成员变量是一个 Map;
  3. 存储旧的引用计数器;
  4. 进行 add 计算,并记录是否有溢出;
  5. 根据是否溢出计算并记录结果,最后返回;

SideTable 引用技术-1

当我们调用[obj release]操作时候,则会调用以下方法

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc = true, bool handleUnderflow = false)

rootRelease核心方法调用了sidetable_release()方法,与retain调用相同,进行逐个字节判断进行循环。

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    auto &refcnt = it.first->second;
    if (it.second) {
        do_dealloc = true;
    } else if (refcnt < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        refcnt |= SIDE_TABLE_DEALLOCATING;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        refcnt -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}

sidetable_release()判断释放条件,当引用计数

define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit

最终,会触发调用dealloc方法。

你可能感兴趣的:(NSObject 底层原理分析(二) -- SideTable)