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 指針生命週期更長一樣。