(二)并不很深入地探究 arc中 __weak 修飾符

關聯文章

(一)并不很深入地探究 arc中 __strong 修飾符
(二)并不很深入地探究 arc中 __weak 修飾符
(三)并不很深入地探究 arc中 __autoreleassing 修飾符

ARC是由編譯器和運行時系統進行內存管理,本文將從編譯器和runtime系統出發,探究 __weak 修飾符的實現過程。

在此之前我們需要知道如何獲取匯編輸出

iOS 獲取匯編輸出方法

對于__weak 修飾符:

  • 若附有__weak 修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量
  • 若使用__weak 修飾符的變量,是否是使用注冊到autoreleasepool中的對象?

接下來針對以上兩點進行整理 (便于理解,下文匯編輸出將整理成偽代碼)

若附有__weak 修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量

id obj = [[NSObject alloc] init];
id __weak obj1 = obj;

匯編輸出偽代碼

id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

通過objc_initWeak初始化帶有__weak修飾符的變量,作用域結束時通過objc_destroyWeak釋放。

objc_initWeakobjc_destroyWeak源碼:

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj); //使用到了storeWeak
}

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);//使用到了storeWeak
}

可見創建weak變量和銷毀變量都用到了storeWeak方法

storeWeak是段函數模板

/* storeWeak 部分代碼 Part 1 */
// 更新weak變量
// 如果 HaveOld 為 true,變量已經存在值,因此需要進行清理操作,變量值可以為空
// 如果 HaveNew 為 true,將有個新值被分配到變量中,這個值可以為空
// 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.
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;

然后SideTable為何物?

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

slock 是一個自旋鎖,用來對SideTable的操作加鎖,refcnts從變量類型看,應該是存儲引用計數的散列表, weak_table 則是存放了弱引用。在weak表中,將對象的地址作為鍵值進行檢索,能夠高速獲取對于__weak修飾符變量的地址。

/* storeWeak 部分代碼 Part 2 */
    // 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;
    }

這段主要是獲取oldObj , oldTable, newTable,并給oldTable, newTable上鎖

/* storeWeak 部分代碼 Part 3 */

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

這段主要確保newObj+initialize被正確調用

/* storeWeak 部分代碼 Part 4 */

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

最后一部分代碼:

  • 第一步: 如果 haveOld為真,調用weak_unregister_no_lock來刪除oldTable中的weak表中記錄
  • 第二步: 如果 haveNew為真,調用weak_register_no_locknewTable中的weak表刪除對應記錄,然后再調用 setWeaklyReferenced_nolock 將引用計數表中做SIDE_TABLE_WEAKLY_REFERENCED標記
  • 最后SideTable釋放鎖,返回newObj

然后,我們回到之前的objc_initWeakobjc_destroyWeak方法,

  • objc_initWeak,調用 storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj)
    因為DontHaveOld為 false ,DoHaveNew為 True, 所以調用了上面第二步
  • objc_destroyWeak調用storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil)
    因為 DoHaveOld 為 true, DontHaveNew為false 所以調用了上面第一步

由上述分析, __weak 通過散列表來實現,如果大量使用會消耗相應的資源(廢棄對象時需要weak表中查找刪除對應記錄,在引用計數表中也要做類似操作),所以只在需要避免循環引用的時候使用__weak修飾符

若使用__weak 修飾符的變量,是否是使用注冊到autoreleasepool中的對象?
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"%@", obj1);

匯編輸出偽代碼

id obj1;
objc_initWeak(&obj1, obj);
id temp = objc_loadWeakRetained(&obj1);
NSLog(@"%@", temp);
objc_release(temp);
objc_destroyWeak(&obj1);

objc_loadWeakRetained部分源碼

if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        assert(cls->isInitialized());
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, SEL_retainWeakReference);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        }
        else {
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }

  • rootTryRetaintryRetain用來進行對變量進行 retain操作, 所以objc_loadWeakRetained函數去除賦有__weak修飾符變量引用的對象,并且retain

然而令人詫異的是,objc_loadWeakRetained值 并沒有加入autoreleasepool,那么它是如何保證使用期間時候obj1存在的呢, 令人費解,本人暫時無法給出解答,希望有大牛來給給予指導。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容