OC Runtime之Weak(3)---探究NSObject

weixin_34162695發表於2017-08-10

Runtime對於weak_table_t還有一層封裝,也就是SideTable。這層封裝對於弱引用機制的主要目的是解決執行緒安全的問題,因為之前也提到weak_table_t的各種操作並不是執行緒安全的。

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<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

RefcountMap用於OC的引用計數機制,此處不表。slock實際上是os_unfair_lock_s型別,用於處理執行緒安全的問題。SideTable提供的方法都與鎖有關。

NSObject的實現中和弱引用相關的的最重要的函式就是storeWeak,其實現如下:

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

首先請忽視C++奇怪的各種語法,比如為什麼template中的列舉會跑到引數裡去(手動哭泣)。haveOld和haveNew主要用來控制具體的操作,比如給一個物件新增一個弱引用,或者是刪除一個弱引用。函式的前半部分主要在處理執行緒安全和類載入的問題,和弱引用的邏輯關係不大。我們從 if (haveOld) { 這一行看起,如果haveOld是true,則說明引數裡的弱引用已經指向了一個物件,需要呼叫weak_unregister_no_lock方法解除關聯。如果haveNew是true,則呼叫weak_register_no_lock關聯目標物件和弱應用,並且將弱引用設定為指向目標物件。因此通過設定haveOld和haveNew不同的組合,可以控制該函式完成不同的操作。

純邏輯來看,這一部分相對簡單,但考慮到執行緒安全,類的初始化,Tagged Pointer等等細節,要處理的地方還是非常多。這一系列的文章主要介紹了弱引用機制的實現,幫助大家對弱引用有一個更詳細更立體的理解,未涉及到太多的具體的底層技術細節,如果讀者有興趣的話,可以繼續深入探究。後續的runtime相關的文章也可能會繼續提及。

相關文章