天天看點

runtime源碼探究(一) weak的實作

weak指針的建立

weak修飾對象不增加其引用計數,apple通過一個hash表來實作對象的弱引用。

在Xcode下編寫如下代碼:

編譯器編譯之後變成類似如下的代碼:

objc_initWeak(&obj1,obj);
           

翻開runtime源碼,NSObject.mm,找到objc_initWeak函數,實作如下:

* @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>(location, (objc_object*)newObj);
}
           

apple對參數已有說明,location是weak指針的位址,newObj是被指向的對象的位址。接下來尋找storeWeak函數,先看apple對storeWeak函數的注釋:

// Update a weak variable.

// If HaveOld is true, the variable has an existing value

// that needs to be cleaned up. This value might be nil.

// If HaveNew is true, there is a new value that needs to be

// assigned into the variable. This value might be nil.

// If CrashIfDeallocating is true, the process is halted if newObj is

// deallocating or newObj’s class does not support weak references.

// If CrashIfDeallocating is false, nil is stored instead.

如果weak指針有指向的對象,那麼清除這個對象,然後weak指針指向新的對象;如果weak指針指向新的對象,那麼就将新的weak引用存起來;如果weak指針指向的對象被釋放了,那麼就不會存儲這個weak引用,直接傳回nil。下面一行行看代碼:

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;
}
           

SideTable是存儲了對象的引用資訊,結構如下:

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

其中weak_table定義如下:

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
           

weak_entries是一個散清單,以對象的位址值為key存儲weak_entry_t對象。

接下來首先判斷weak指針有沒有指向舊的對象:

if (HaveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
           

如果有,就取出來,然後将該對象從舊的weak表中删除:

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

然後再将weak指針指向新的對象,存入weak表中。

如果weak指針指向的是一個新的對象,那麼直接存入對象的weak表中:

// 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.
    }
           

其中,weak_register_no_lock函數以被指向的對象的位址為key,将weak指針存入weak表。再看weak_register_no_lock函數的實作:

**整理後關鍵邏輯如下:**
    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry;
        new_entry.referent = referent;
        new_entry.out_of_line = ;
        new_entry.inline_referrers[] = referrer;
        for (size_t i = ; i < WEAK_INLINE_COUNT; i++) {
            new_entry.inline_referrers[i] = nil;
        }

        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
           

其中weak_entry_t定義如下:

#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};
           

weak_entry_t對象裡邊存儲着被弱引用的對象,裡面weak_referrer_t變量也是一個hash表,存儲着該對象的若引用指針。如果弱引用數目小于4個,那麼weak_entry_t就通過數組來存儲weak指針,如果超過4個,那麼就通過hash表來存儲weak指針。

回到weak_register_no_lock函數,如果發現被引用的對象有弱引用,那麼直接取出weak_entry_t對象,然後将新的weak指針存到weak_referrer_t指向的數組或者hash表中,如果存的是hash表,那麼key值是weak指針的位址;如果發現對象之前沒有弱引用,那麼就建立立一個weak_entry_t對象,然後将weak指針存入到其中。

weak指針的銷毀

再來看weak是在什麼時候銷毀并被置為空的。找到dealloc函數的實作,其中dealloc主要調用了objc_destructInstance函數:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa = obj->getIsa();

        if (isa->hasCxxDtor()) {
            object_cxxDestruct(obj);
        }

        if (isa->instancesHaveAssociatedObjects()) {
            _object_remove_assocations(obj);
        }

        if (!UseGC) objc_clear_deallocating(obj);
    }

    return obj;
}
           

objc_destructInstance函數主要做了三件事:

1. 尋找一個名為 ._cxx_destruct 的函數,這一步主要是由編譯器插入代碼實作,這裡銷毀了對象的執行個體變量,并且調用了父類的dealloc(ARC環境下)。

2. 調用_object_remove_assocations函數清除對象的所有關聯對象。

3. 調用objc_clear_deallocating函數清除所有的weak指針被将其置為nil。

objc_clear_deallocating函數裡主要調用了sidetable_clearDeallocating函數來清除弱引用,裡邊通過weak_clear_no_lock函數來實作:

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// 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) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

    for (size_t i = ; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = 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);
}
           

我們可以清楚的看到,這裡清除了對象所有的weak指針并将其置為nil,同時從weak表中清除了對應的weak_entry_t對象。

weak和aoturelease

即使将weak指針變量注冊到aotureleasepool中,該對象一樣有效,這是因為在weak指針變量注冊到aotureleasepool中時,系統調用了objc_loadWeakRetained函數,将weak指針變量的引用計數加1,然後又對其發送了objc_autorelease消息,以保證aotureleasepool聲明周期中其值可用。