Weak 指針源碼學習

    weak 指針,大家都不陌生。之前也簡單學習了一下,總結了一下對象在回收時 weak 指針是如何被 nil 掉的。不過我對 weak 指針的一些方面還有一些疑問,所以去看了一下源碼。我的疑問主要是:

  • weak 指針使用時,會對所指對象進行 retain 嗎,還是需要我們手動的去做這件事,如何保證指針使用期間所指對象不被 dealloc 的?
  • 爲什麼要用 hash set 存 weak 指針?目的是什麼?用 array 不行嗎?

一、 weak 指針使用時,會對所指對象進行 retain 嗎,還是會 retain + autorelease ?還是需要我們手動的去做這件事,如何保證指針使用期間所指對象不被 dealloc 的

/** 
 * This function gets called when the value of a weak pointer is being 
 * used in an expression. Called by objc_loadWeakRetained() which is
 * ultimately called by objc_loadWeak(). The objective is to assert that
 * there is in fact a weak pointer(s) entry for this particular object being
 * stored in the weak-table, and to retain that object so it is not deallocated
 * during the weak pointer's usage.
 * 
 * @param weak_table 
 * @param referrer The weak pointer address. 
 */
/*
  Once upon a time we eagerly cleared *referrer if we saw the referent 
  was deallocating. This confuses code like NSPointerFunctions which 
  tries to pre-flight the raw storage and assumes if the storage is 
  zero then the weak system is done interfering. That is false: the 
  weak system is still going to check and clear the storage later. 
  This can cause objc_weak_error complaints and crashes.
  So we now don't touch the storage until deallocation completes.
*/
id 
weak_read_no_lock(weak_table_t *weak_table, id *referrer_id) 
{
    objc_object **referrer = (objc_object **)referrer_id;
    objc_object *referent = *referrer;
    if (referent->isTaggedPointer()) return (id)referent;

    weak_entry_t *entry;
    if (referent == nil  ||  
        !(entry = weak_entry_for_referent(weak_table, referent))) 
    {
        return nil;
    }

    if (! referent->ISA()->hasCustomRR()) {
        if (! referent->rootTryRetain()) {
            return nil;
        }
    }
    else {
        BOOL (*tryRetain)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_retainWeakReference);
        if ((IMP)tryRetain == _objc_msgForward) {
            return nil;
        }
        if (! (*tryRetain)(referent, SEL_retainWeakReference)) {
            return nil;
        }
    }

    return (id)referent;
}

    如註釋,我們在使用 weak 指針的時候,會調用這個函數。如果是 taggedPointer ,直接返回 referent。然後驗證 weak 指針是存在的。最後 retain 所指對象。並沒有 autorelease 。這一點可以通過 po [NSAutoreleasePool showPools] 命令來 debug pool 中的對象。我 debug 的結果是使用 weak 指針並不會把所指對象加入 autoreleasepool。至於引用計數是如何平衡的,我通過 Product > Perform Action > Assemble “xxx.x” 命令去看彙編源碼,我的理解是編譯器會在 weak 指針使用完時插入 _objc_release。如下所示

Ltmp82:
	.loc	8 173 24 prologue_end   ; xxx.m:173:24
	add	x9, x8, #32             ; =32
	mov	 x0, x9
	bl	_objc_loadWeakRetained
	adrp	x8, l_OBJC_SELECTOR_REFERENCES_.115@PAGE
	add	x8, x8, l_OBJC_SELECTOR_REFERENCES_.115@PAGEOFF
	.loc	8 173 23 is_stmt 0      ; xxx.m:173:23
	ldr		x1, [x8]
	mov	 x8, x0
	.loc	8 173 23                ; xxx.m:173:23
	str		x0, [sp]        ; 8-byte Folded Spill
	mov	 x0, x8
	bl	_objc_msgSend
	.loc	8 173 16                ; xxx.m:173:16
	; InlineAsm Start
	mov	 x29, x29	; marker for objc_retainAutoreleaseReturnValue
	; InlineAsm End
	.loc	8 173 16                ; xxx.m:173:16
	bl	_objc_retainAutoreleasedReturnValue
	.loc	8 173 16                ; xxx.m:173:16
	str	x0, [sp, #16]
	ldr		x0, [sp]        ; 8-byte Folded Reload
	.loc	8 173 16                ; xxx.m:173:16
	bl	_objc_release
	add	x0, sp, #16             ; =16
	mov	x8, #0
第 173 行代碼是
id test = [weakSelf testAutorelease];

二、 爲什麼要用 hash set 存 weak 指針地址?目的是什麼?用 普通 array 不行嗎?

/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line==0, the set is instead a small inline array.
 */
#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];
        };
    };
};

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

    看一下 weak table 的數據結構。是個 hash table,key 是對象地址,value 是一個結構體,包括對象地址以及 weak 指針的地址的數組。我之前疑惑的這個數組爲什麼是個 hash 數組,而不是一個普通的數組。我的點是,爲什麼要 hash,hash 是爲了查找效率,那查找爲了什麼,爲了確定在不在 weak table 中?所指對象被回收,weak 指針不是會 nil 嗎? 沒有 nil 就代表有效唄。還可能是爲了什麼,最終想到是爲了刪除!referer 可能先於 referent 被回收,比如超出作用範圍,被nil,像普通指針的內存被回收之前把 referent 的 referenceCount 減一,weak 指針的內存被回收前,要把自己從 weak table 中刪除。如果這種情況發生,referent 被回收時,再去 nil 掉 referer 就會造成內存訪問錯誤,BAD_ACCESS。所以這個疑惑不難解開。referer 需要單獨刪除,而不是只需要在 referent dealloc 時一併刪除。referer 的生命週期不一定長於 referent,只是我們說到 weak 指針總是關注 referent 被回收時,referer 會被 nil,覺得 weak 指針生命週期更長一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章