對象引用計數保存在哪裏?

在64bit操作系統中,apple對對象中的isa進行了優化使用isa_t結構來保存關於對象的更多信息.

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      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)

# else
#   error unknown architecture for packed isa
# endif

其中:extra_rc和has_sidetable_rc兩個標誌就是用來存儲對象引用計數的.

引用計數的存儲

引用計數的增加主要通過retain()函數來實現.

// Equivalent to calling [this retain], with shortcuts if there is no override
inline id 
objc_object::retain()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        //在ARC中直接調用
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    //是否將引用計數結果轉存到sidetable中
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //不支持nonpointer:引用計數保存在sidetable中
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            //不嘗試增加引用計數且sidetable被鎖,則打開sidetable鎖定
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            //嘗試增加引用計數:如果嘗試成功則返回當前對象;如果嘗試失敗則返回nil
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            //直接進行retain操作:在sidetable中使引用計數增加1
            else return sidetable_retain();
        }
        
        //支持nonpointer

        // 嘗試增加引用計數且對象正在釋放
        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++

        //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();
            //sidetable加鎖
            sideTableLocked = true;
            //引用計數轉存到sidetable中
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
  • 在不支持nonpointer的對象中,引用計數只存儲在sidetable中;
  • 可以看出在支持nonpointer的情況下:
    • 在isa的位域extra_rc足以存儲的情況,引用計數會優先選擇存儲在isa的extra_rc位域中;
    • 若isa位域extra_rc溢出,則會選擇將將引用計數的RC_HALF(如果extra_rc佔據8bit,則RC_HALF=2^7)保存在isa中,另一半RC_HALF疊加保存在sidetable中.之所以這樣選擇是因爲isa_t的存取理論上會比sidetable的操作效率上快很多,這樣做既可以使超出extra_rc存儲範圍的引用計數得到有效存儲,又可以確保引用計數的增減足夠快速(存取都以extra_rc優先).

引用計數的獲取

因爲對象的引用計數主要存儲在isa位域extra_rc和散列表中,所以要獲取對象的引用計數也需要從兩個位置進行獲取.

uintptr_t
_objc_rootRetainCount(id obj)
{
    assert(obj);

    return obj->rootRetainCount();
}

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    //添加sidetable操作鎖
    sidetable_lock();
    //加載對象isa
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    //如果對象開啓了nonpointer
    if (bits.nonpointer) {
        //獲取isa中存儲的引用計數
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            //獲取散列表中存儲的引用計數
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    //對象未開啓nonpointer
    sidetable_unlock();
    return sidetable_retainCount();
}



uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

  • 在開啓nonpointer的對象中,對象的引用計數包括兩部分:
    • 存儲在isa中的引用計數;
    • 存儲在sidetable中的引用計數.
  • 在未開啓nonpointer的對象中,對象的引用計數全部存儲在sidetable中,只需要從sidetable中獲取就可以.

這裏有一個有意思的問題:

  • 在開啓nonpointer的對象中,引用計數retainCount=1+isa.extra_rc+sidetable_getExtraRC_nolock();
  • 在未開啓nonpointer的對象中,引用計數retainCount=1+table.refcnts.find(this)->second >> SIDE_TABLE_RC_SHIFT;

所以其實在創建對象的過程中(例如alloc和copy),存儲的引用計數並沒有進行+1操作,而是默認對象的最小引用計數爲1.在release操作時纔會判斷引用計數-1是否爲0,如果爲0則進行釋放;否則進行引用計數-1.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章