內(nèi)存管理-(四)弱引用表

Q: 一個weak修飾的變量時怎么被加入到弱引用表中的?來看一個代碼塊:

{
    id __weak obj1 = obj;
}

// 編譯后

{
    id obj1;
    objc_initWeak(&obj1, obj);
}
// 在這個過程中,發(fā)生了什么?

我們先來看看objc_initWeak調(diào)用了什么方法。

Xnip2018-10-25_17-30-17.png

我們從源碼可以得知其中的調(diào)用順序??疵Q可以得知,具體的注冊弱引用的步驟是在weak_register_no_lock內(nèi)部的?,F(xiàn)在我們具體分析一下每一步的函數(shù)都做了什么。

// 這個方法傳遞了2個參數(shù)值,一個是要指向弱引用對象的對象,一個是需要被弱引用的對象。
/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    // 這個方法內(nèi)部就做了一個非空判斷,然后直接走到storeWeak方法中
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 這里使用了C++的模板,DontHaveOld(無老對象),DoHaveNew(有新對象),DoCrashIfDeallocating(銷毀過程中不Crash)
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

接下來我們看看storeWeak方法的實現(xiàn),這里因為我們在上面的objc_initWeak傳入的參數(shù)是無老對象,有新對象,所以我們按照上面?zhèn)鲄⒌倪壿嫹治鱿旅娴拇a。

enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating 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) {  // 我們沒有old所以這里直接過
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {  // 有新對象,走這里
        // 從SideTables當(dāng)中,拿到newObj所在的表,賦值給newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) { // 我們沒有old所以這里直接過
        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) {  // 有新對象,且傳遞進來的newObj是有值的
        Class cls = newObj->getIsa();   // 根據(jù)newObj的isa指針 找到類對象
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized())  // 判斷類是否已經(jīng)初始化
        {   // 已經(jīng)初始化過了 這里面的內(nèi)容不影響注冊weak
            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) { // 我們沒有old所以這里直接過
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        /* 這里就是我們在上圖中看到的weak_register_no_lockg方法,這個函數(shù)接收4個參數(shù)
            1. weak_table_t *weak_table,    弱引用表
            2. id referent_id,              需要被引用的對象
            3. id *referrer_id,             弱引用指針
            4. bool crashIfDeallocating,    對象在廢棄的過程中,Crash的一個標(biāo)志位
         */
        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()) {
            // 新對象有值 且不是小對象的指針類型 就設(shè)置這個對象有弱引用的標(biāo)志位
            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;

到這里,已經(jīng)可以大致了解弱引用大致的注冊流程了,我再來看看weak_register_no_lock中所做的操作

...
// 我們重點看這里
if ((entry = weak_entry_for_referent(weak_table, referent))) {
    // 將新的弱引用指針添加到弱引用數(shù)組當(dāng)中
    append_referrer(entry, referrer);
} 
else {  // 如果沒有獲取到弱引用數(shù)組,則重新創(chuàng)建,然后添加
    weak_entry_t new_entry(referent, referrer);
    weak_grow_maybe(weak_table);
    weak_entry_insert(weak_table, &new_entry);
}
...

現(xiàn)在再看看系統(tǒng)是如何查找到弱引用表中的弱引用數(shù)組的

/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);
    // 拿到弱引用結(jié)構(gòu)體數(shù)組
    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    // 通過Hash算法根據(jù)原對象地址找到對應(yīng)的索引位置
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    // 這個while用來解決Hash沖突,如果找到的位置不是當(dāng)前要查找的對象,會根據(jù)沖突算法來移動索引位置,直到找到要查找的對象
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    // 找到了就返回弱引用表
    return &weak_table->weak_entries[index];
}

總結(jié)一下這個流程。被weak修飾的變量,系統(tǒng)會在編譯時調(diào)用objc_initWeak方法,然后調(diào)用storeWeak,再調(diào)用weak_register_no_\lock,在這個方法中,會根據(jù)對象的地址通過Hash算法計算出位置,然后插入到弱引用表中。

Q: 當(dāng)一個對象釋放,weak變量是怎么處理的?

Xnip2018-10-25_19-15-51.png

我們之前已經(jīng)知道了大致的調(diào)用流程,現(xiàn)在我們看看weak_clear_no_lock方法是怎么實現(xiàn)的

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void
// 這個對象有2個參數(shù) 一個是弱引用表 一個是需要被清除引用的對象
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
    // 定義一個局部變量 用referent_id賦值
    objc_object *referent = (objc_object *)referent_id;
    // 找到對應(yīng)的弱引用數(shù)組
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) { // 如果沒有 則當(dāng)前對象沒有弱引用 不用處理 直接返回
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) { // 如果弱引用列表元素個數(shù)大于4走這里
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else { // 如果弱引用列表元素個數(shù)小于4走這里
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    // 到這里 referrers 就取到了當(dāng)前對象對應(yīng)的弱引用列表
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {// 如果弱引用指針存在
            if (*referrer == referent) { // 這個弱引用代表的地址就是當(dāng)前對象的地址
                *referrer = nil;    // 弱引用指針置為nil
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

這里總結(jié)一下。當(dāng)一個對象被dealloc,在dealloc的內(nèi)部實現(xiàn)中,會調(diào)用weak_clear_no_lock方法。這個方法會在弱引用表中找到要被銷毀的對象,然后把當(dāng)前對象相對應(yīng)的弱引用都取出來。置為nil。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容