1. 程式人生 > >weak引用表原理探究

weak引用表原理探究

一、weak引用實現原理探究

  首先對《Xcode 10 下如何除錯objc4-723》建立的objc原始碼除錯工程表示感謝!

  地址:https://www.jianshu.com/p/9e0fc8295c4b

 

  大多數文章闡述了基本過程:

1.初始化一個weak物件時,runtime會呼叫一個objc_initWeak函式,初始化一個新的weak指標指向該物件的地址

2.在objc_initWeak函式中會繼續呼叫objc_storeWeak函式,在這個過程是用來更新weak指標的指向,同時建立對應的弱引用表

3.在物件釋放時,會呼叫clearDeallocating函式,這個函式會根據物件地址獲取所有weak指標陣列,然後遍歷這個陣列置為nil。最後把該條物件的記錄從weak表中刪除。

  

id objc_initWeak(id *location, id newObj) {
    // 檢視物件例項是否有效
    // 無效物件直接導致指標釋放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 這裡傳遞了三個 bool 數值
    // 使用 template 進行常量引數傳遞是為了優化效能
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(HaveOld  ||  HaveNew);
    if (!HaveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);

    if (HaveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (HaveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, 
                                                      (id)newObj, location, 
                                                      CrashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);

    return (id)newObj;
}

  其中涉及到一個數據結構

struct SideTable {
    spinlock_t slock; // 因為操作物件的引用計數頻率很快,因此係統在這裡設定了一把自旋鎖,保證是原子操作
    RefcountMap refcnts; // 引用計數器雜湊表,根據物件地址查詢物件的引用計數
    weak_table_t weak_table; // 維護weak指標的結構體
}

  通過下面的程式碼取得

  

  也就是全域性的sidetables本身是一個hash表,總共大小為64;每一個value對應的是 sidetable,sidetable中儲存引用計數表和weak引用表

  

   找到一個sidetable表之後,要根據weak所指物件的地址hash值,找到對應儲存weak指標的value結構體

  

  接下來的操作就是修改weak引用表了